Verified Commit 4f02474d authored by Davide Depau's avatar Davide Depau
Browse files

Merge branch 'fixes' into HEAD

parents 0ea3d631 4927abe8
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# Docker compose overrides
docker-compose.*.yml
FROM alpine:latest
RUN apk add -U python3 py3-gunicorn
RUN mkdir /uus
WORKDIR /uus
COPY . /uus
COPY requirements.txt /uus
RUN pip3 install -r requirements.txt
RUN apk add -U python3 py3-gunicorn && \
pip3 install /uus && \
rm -Rf /uus && \
ln -s "$(which flask)" /usr/bin/uus
ENV FLASK_APP=uus
COPY uus.py /uus
COPY admin /uus/admin
......
recursive-include uus/templates *
recursive-include uus/static *
## UUS - Uncomplicated Url Shortener
This is a url shortener written in python with flask
# UUS - Uncomplicated Url Shortener
This is a url shortener written in python with flask
## Management CLI
A simple CLI is provided to add, modify and delete admin users. `REDIS_URL` must be provided unless it is the default. Also, `FLASK_APP=uus` must be set when not running in Docker (the Dockerfile already sets this).
Run `flask` to get the list of commands.
In the docker container you can run `uus` directly to get to the CLI, for example
```
$ docker exec -it uus_uus_1 uus adduser admin
Password:
```
\ No newline at end of file
services:
uus:
build: .
......@@ -9,9 +9,13 @@ services:
- redis_db
ports:
- "9001:8000"
redis_db:
image: redis:alpine
restart: always
entrypoint: redis-server --appendonly yes
volumes:
- /opt/uus:/data
- uus_redis:/data
volumes:
uus_redis: {}
import setuptools
setuptools.setup(name='uus',
version='1.0',
description='Uncomplicated URL Shortener',
long_description=open('README.md').read().strip(),
author='JackV',
author_email='1vercesig@gmail.com',
url='https://gitlab.poul.org/jackv/uus',
packages=['uus'],
install_requires=['Flask', 'Flask-Redis', 'Flask-HTTPAuth'],
license='MIT License',
zip_safe=False,
include_package_data=True,
keywords='url shortener',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: Flask',
'Operating System :: OS Independent',
'License :: OSI Approved :: MIT License',
'Topic :: Internet :: WWW/HTTP',
])
#!/usr/bin/env python3
import flask
from flask_redis import FlaskRedis
from flask_httpauth import HTTPBasicAuth
import re
__all__ = ('app', 'redis_store', 'management_cli')
import os
import crypt
import re
import flask
from flask_httpauth import HTTPBasicAuth
from flask_redis import FlaskRedis
app = flask.Flask(__name__)
auth = HTTPBasicAuth()
......@@ -13,7 +13,18 @@ app.config['REDIS_URL'] = os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/0'
redis_store = FlaskRedis()
redis_store.init_app(app)
pure_string = re.compile(r'^\w+$')
admin_dir = os.path.join(os.path.dirname(__file__), 'admin')
def redis_key_for_user(user: str) -> str:
return "user.{}".format(user)
def redis_key_for_url(url: str) -> str:
return "url.{}".format(url)
def redis_key_for_count(url: str) -> str:
return "count.{}".format(url)
def handle_redirect(path):
......@@ -30,7 +41,7 @@ def handle_redirect(path):
@auth.verify_password
def handle_login(username, password):
crypt_pw = redis_store.get("user." + username)
crypt_pw = redis_store.get(redis_key_for_user(username))
if crypt_pw is not None:
crypt_pw = crypt_pw.decode()
return crypt_pw == crypt.crypt(password, crypt_pw)
......@@ -41,17 +52,7 @@ def handle_login(username, password):
@app.route('/admin')
@auth.login_required
def adminui():
return flask.send_from_directory(admin_dir,
'admin.html',
as_attachment=False)
@app.route('/admin/<path:path>')
@auth.login_required
def admin_files(path):
return flask.send_from_directory(admin_dir,
path,
as_attachment=False)
return flask.render_template("admin.html")
@app.route('/api/v1/addurl', methods=['POST'])
......@@ -64,9 +65,9 @@ def add_url():
long_url = payload['url']
if not pure_string.match(short_url):
return flask.abort(400)
if not redis_store.exists('url.' + short_url):
redis_store.set('count.' + short_url, 0)
redis_store.set('url.' + short_url, long_url)
if not redis_store.exists(redis_key_for_url(short_url)):
redis_store.set(redis_key_for_count(short_url), 0)
redis_store.set(redis_key_for_url(short_url), long_url)
return ''
except KeyError:
return flask.abort(400)
......@@ -80,8 +81,8 @@ def del_url():
payload = flask.request.get_json()
if payload:
try:
redis_store.delete('url.' + payload['name'])
redis_store.delete('count.' + payload['name'])
redis_store.delete(redis_key_for_url(payload['name']))
redis_store.delete(redis_key_for_count(payload['name']))
return ''
except KeyError:
return flask.abort(400)
......@@ -97,19 +98,18 @@ def list_urls():
for key in keys:
real_key = key.replace('url.', '', 1)
return_dict[real_key] = {'url': redis_store.get(key).decode(),
'count': int(redis_store.get('count.' + real_key).decode())}
'count': int(redis_store.get(redis_key_for_count(real_key)).decode())}
return flask.jsonify(return_dict)
@app.route('/<path:path>')
def redirect_pure(path):
return handle_redirect(path)
@app.route('/<path:path>/')
def redirect_slash(path):
return handle_redirect(path)
@app.route('/<path:path>', strict_slashes=False)
def redirect(path):
if pure_string.match(path):
return handle_redirect(path)
else:
return flask.abort(404)
from .management_cli import *
if __name__ == "__main__":
app.run()
from . import app
if __name__ == "__main__":
app.run()
import crypt
import click
from . import app, redis_store, redis_key_for_user
@app.cli.command()
@click.argument("username")
@click.option("--passwd", "-p", prompt="Password", help="Password", hide_input=True)
def adduser(username, passwd):
"""Add UUS admin user"""
cryptpwd = crypt.crypt(passwd)
redis_store.set(redis_key_for_user(username), cryptpwd)
@app.cli.command()
@click.argument("username")
@click.option("--passwd", "-p", prompt="Password", help="Password", hide_input=True)
def passwd(username, passwd):
"""Change UUS admin user password"""
adduser(username, passwd)
@app.cli.command()
@click.argument("username")
def deluser(username):
"""Delete UUS admin user"""
redis_store.delete(redis_key_for_user(username))
<html>
<head>
<title>UUS Admin Area</title>
<link rel="stylesheet" href="admin/admin.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
</head>
<body>
<div>
......@@ -25,7 +25,7 @@
</table>
</div>
</div>
<script src="admin/jquery-3.3.1.min.js"></script>
<script src="admin/admin.js"></script>
<script src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
</body>
</html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment