Short URL | +
---|
{{request.host_url}}{{ shortcode }} | +
Add a shortcode
++ + +
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..0783579
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,36 @@
+# Build an intermediate image for the Python requirements
+FROM python:3.9-slim-buster as intermediate
+
+# Install Git and Python requirements
+RUN apt update && apt install -y build-essential
+COPY requirements.txt .
+RUN python -m pip install --user -r requirements.txt
+
+# Start from a fresh image
+FROM python:3.9-slim-buster
+
+# Keeps Python from generating .pyc files in the container
+ENV PYTHONDONTWRITEBYTECODE=1
+
+# Turns off buffering for easier container logging
+ENV PYTHONUNBUFFERED=1
+
+RUN useradd -u 4000 -d /app appuser
+WORKDIR /app
+
+# Copy over the Python requirements from the intermediate container
+COPY --from=intermediate /root/.local /app/.local
+
+RUN chown -R appuser: /app
+
+USER appuser
+
+# Set the main execution command
+ENTRYPOINT [".local/bin/waitress-serve", "main:app"]
+# HEALTHCHECK --interval=5s --timeout=30s --start-period=5s --retries=3 CMD [ "/app/healthcheck.sh" ]
+EXPOSE 8080/tcp
+
+# Copy in the code
+COPY slinky slinky/
+COPY templates templates/
+COPY main.py .
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..55f691f
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,21 @@
+---
+version: "3"
+services:
+ slinky:
+ container_name: slinky
+ build:
+ context: .
+ image: slinky:latest
+ networks:
+ - traefik
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.slinky.entrypoints=web
+ - traefik.http.routers.slinky.service=slinky
+ - traefik.http.services.slinky.loadbalancer.server.port=8080
+ restart: unless-stopped
+
+networks:
+ traefik:
+ external:
+ name: traefik_internal
diff --git a/main.py b/main.py
index d9a5362..2b6e1e4 100644
--- a/main.py
+++ b/main.py
@@ -1,16 +1,23 @@
-import sys
+"""
+Main Flask-based app for Slinky
+"""
+from flask import Flask, render_template
+from flask_bootstrap import Bootstrap
-from flask.wrappers import Response
+from slinky.web import slinky_webapp
+
+app = Flask(__name__)
+app.register_blueprint(slinky_webapp)
+
+Bootstrap(app)
-def url_home() -> Response:
- ...
+@app.route('/')
+def index() -> str:
+ """
+ Index/Landing page
-
-if __name__ == '__main__':
-
- def main() -> int:
- # Start Flask
- return 0
-
- sys.exit(main())
+ Returns:
+ str: string of page content
+ """
+ return render_template('index.html')
diff --git a/requirements.txt b/requirements.txt
index 7e10602..84548f4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,5 @@
flask
+flask_bootstrap
+flask_wtf
+waitress
+Flask-SQLAlchemy
diff --git a/slinky/__init__.py b/slinky/__init__.py
index 6899629..0085bd3 100644
--- a/slinky/__init__.py
+++ b/slinky/__init__.py
@@ -1,7 +1,58 @@
+"""
+Main code
+"""
+
import random
import string
+from datetime import datetime
+from typing import Optional
+
+from slinky import db
+
def random_string(length: int = 4) -> str:
+ """
+ Create a random, alphanumeric string of the length specified
+
+ Args:
+ length (int, optional): length of string to generate. Defaults to 4.
+
+ Returns:
+ str: random alphanumeric string
+ """
allowed_chars: str = string.ascii_letters + string.digits
return ''.join(random.SystemRandom().choice(allowed_chars) for _ in range(length))
+
+
+def add_shortcode(
+ url: str,
+ length: int = 4,
+ fixed_views: int = 0,
+ expiry: datetime = datetime.max,
+) -> str:
+ """
+ Add a shortcode to the DB
+
+ Args:
+ url (str): URL to redirect to
+ fixed_views (int, optional): number of views to serve before expiring.
+ Defaults to 0 (no limit).
+ expiry (int, optional): date of expiry. Defaults to 0 (no limit).
+
+ Returns:
+ str: shortcode for the redirect
+ """
+ session = db.session()
+ shortcode = random_string(length=length)
+ dbentry = db.ShortURL(
+ shortcode=shortcode,
+ url=url,
+ fixed_views=fixed_views,
+ expiry=expiry,
+ )
+ session.add(dbentry)
+ session.commit()
+ session.close()
+
+ return shortcode
diff --git a/slinky/db.py b/slinky/db.py
new file mode 100644
index 0000000..3a4c1de
--- /dev/null
+++ b/slinky/db.py
@@ -0,0 +1,35 @@
+"""
+DB component
+"""
+from sqlalchemy import Column, Integer, String, create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import Session, sessionmaker
+
+Base = declarative_base()
+
+
+class ShortURL(Base): # pylint: disable=too-few-public-methods
+ """
+ Class to describe the DB schema for ShortURLs
+ """
+
+ __tablename__ = 'shorturl'
+
+ id = Column(Integer, primary_key=True)
+ shortcode = Column(String(128), unique=True, nullable=False)
+ url = Column(String(2048), unique=False, nullable=False)
+ fixed_views = Column(Integer, unique=False, nullable=False)
+ expiry = Column(Integer, unique=False, nullable=False)
+
+
+def session() -> Session:
+ """
+ Create a DB session
+
+ Returns:
+ Session: the DB session object
+ """
+ engine = create_engine('sqlite:////tmp/test.db')
+ Base.metadata.create_all(engine)
+ new_session = sessionmaker(bind=engine)
+ return new_session()
diff --git a/slinky/templates/add.html b/slinky/templates/add.html
new file mode 100644
index 0000000..7f30f7a
--- /dev/null
+++ b/slinky/templates/add.html
@@ -0,0 +1,39 @@
+{% include '_head.html' %}
+
+
+Add a shortcode
+
+
+
+
+
+
+ {% endif -%}
+
+
+
+
+ Short URL
+
+
+
+ {{request.host_url}}{{ shortcode }}
+