From a5cdf8867ae5837155f3a80fcd0e98549a254343 Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Sat, 29 Mar 2025 19:05:31 +0100 Subject: [PATCH] move Git repository kontor-docker to directory bottle-docker --- bottle-docker/.gitignore | 129 ++++++++++++ bottle-docker/README.md | 2 + bottle-docker/app/Dockerfile | 19 ++ bottle-docker/app/requirements.txt | 2 + bottle-docker/app/src/comics/__init__.py | 193 ++++++++++++++++++ bottle-docker/app/src/comics/artistDAO.py | 49 +++++ bottle-docker/app/src/comics/comicDAO.py | 52 +++++ bottle-docker/app/src/comics/models.py | 121 +++++++++++ bottle-docker/app/src/comics/publisherDAO.py | 49 +++++ bottle-docker/app/src/comics/storyArcDAO.py | 49 +++++ bottle-docker/app/src/kontor.css | 89 ++++++++ bottle-docker/app/src/kontor.py | 148 ++++++++++++++ bottle-docker/app/src/sessionDAO.py | 64 ++++++ bottle-docker/app/src/userDAO.py | 75 +++++++ bottle-docker/app/src/views/artist_list.tpl | 39 ++++ .../app/src/views/artist_template.tpl | 43 ++++ bottle-docker/app/src/views/comic_index.tpl | 34 +++ bottle-docker/app/src/views/comic_list.tpl | 44 ++++ .../app/src/views/comic_template.tpl | 51 +++++ bottle-docker/app/src/views/kontor.tpl | 27 +++ bottle-docker/app/src/views/login.tpl | 52 +++++ .../app/src/views/publisher_list.tpl | 39 ++++ .../app/src/views/publisher_template.tpl | 42 ++++ bottle-docker/app/src/views/signup.tpl | 59 ++++++ bottle-docker/app/src/views/storyarc_list.tpl | 40 ++++ .../app/src/views/storyarc_template.tpl | 43 ++++ bottle-docker/docker-compose.yaml | 31 +++ bottle-docker/front-end/index.html | 82 ++++++++ bottle-docker/nginx/nginx.conf | 14 ++ 29 files changed, 1681 insertions(+) create mode 100644 bottle-docker/.gitignore create mode 100644 bottle-docker/README.md create mode 100644 bottle-docker/app/Dockerfile create mode 100644 bottle-docker/app/requirements.txt create mode 100644 bottle-docker/app/src/comics/__init__.py create mode 100644 bottle-docker/app/src/comics/artistDAO.py create mode 100644 bottle-docker/app/src/comics/comicDAO.py create mode 100644 bottle-docker/app/src/comics/models.py create mode 100644 bottle-docker/app/src/comics/publisherDAO.py create mode 100644 bottle-docker/app/src/comics/storyArcDAO.py create mode 100644 bottle-docker/app/src/kontor.css create mode 100644 bottle-docker/app/src/kontor.py create mode 100644 bottle-docker/app/src/sessionDAO.py create mode 100644 bottle-docker/app/src/userDAO.py create mode 100644 bottle-docker/app/src/views/artist_list.tpl create mode 100644 bottle-docker/app/src/views/artist_template.tpl create mode 100644 bottle-docker/app/src/views/comic_index.tpl create mode 100644 bottle-docker/app/src/views/comic_list.tpl create mode 100644 bottle-docker/app/src/views/comic_template.tpl create mode 100644 bottle-docker/app/src/views/kontor.tpl create mode 100644 bottle-docker/app/src/views/login.tpl create mode 100644 bottle-docker/app/src/views/publisher_list.tpl create mode 100644 bottle-docker/app/src/views/publisher_template.tpl create mode 100644 bottle-docker/app/src/views/signup.tpl create mode 100644 bottle-docker/app/src/views/storyarc_list.tpl create mode 100644 bottle-docker/app/src/views/storyarc_template.tpl create mode 100644 bottle-docker/docker-compose.yaml create mode 100644 bottle-docker/front-end/index.html create mode 100644 bottle-docker/nginx/nginx.conf diff --git a/bottle-docker/.gitignore b/bottle-docker/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/bottle-docker/.gitignore @@ -0,0 +1,129 @@ +# 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/ +pip-wheel-metadata/ +share/python-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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/bottle-docker/README.md b/bottle-docker/README.md new file mode 100644 index 0000000..2c49794 --- /dev/null +++ b/bottle-docker/README.md @@ -0,0 +1,2 @@ +# kontor-bottle +Kontor with Python Bottle Framework diff --git a/bottle-docker/app/Dockerfile b/bottle-docker/app/Dockerfile new file mode 100644 index 0000000..2974a07 --- /dev/null +++ b/bottle-docker/app/Dockerfile @@ -0,0 +1,19 @@ +# set base image (host OS) +FROM python:3.8 + +# set the working directory in the container +WORKDIR /code + +# copy the dependencies file to the working directory +COPY requirements.txt /code/ + +# copy the content of the local src directory to the working directory +COPY src/ /code/ + +# install dependencies +RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt + +EXPOSE 9000 + +# command to run on container start +CMD [ "python", "./kontor.py" ] diff --git a/bottle-docker/app/requirements.txt b/bottle-docker/app/requirements.txt new file mode 100644 index 0000000..8e5db27 --- /dev/null +++ b/bottle-docker/app/requirements.txt @@ -0,0 +1,2 @@ +pymongo +bottle diff --git a/bottle-docker/app/src/comics/__init__.py b/bottle-docker/app/src/comics/__init__.py new file mode 100644 index 0000000..6afff02 --- /dev/null +++ b/bottle-docker/app/src/comics/__init__.py @@ -0,0 +1,193 @@ +# -*- coding:utf-8 -*- +import pymongo +import comics.publisherDAO +import comics.artistDAO +import comics.comicDAO +import bottle +import cgi + + +__author__ = 'tpeetz' + + +class Plugin: + + def __init__(self, app, database, sessions): + self.app = app + self.db = database + self.sessions = sessions + self.publishers = publisherDAO.PublisherDAO(database) + self.artists = artistDAO.ArtistDAO(database) + self.comics = comicDAO.ComicDAO(database) + self.routing() + + + def routing(self): + self.app.route('/comics/', 'GET', self.comic_index) + self.app.route('/comics/comic', 'GET', self.comic_list) + self.app.route('/comics/comic/', 'GET', self.comic_details) + self.app.route('/comics/comic/create', 'GET', self.get_comic_create) + self.app.route('/comics/comic/create', 'POST', self.post_create_comic) + self.app.route('/comics/publisher', 'GET', self.publisher_list) + self.app.route('/comics/publisher/', 'GET', self.publisher_details) + self.app.route('/comics/publisher/create', 'GET', self.get_publisher_create) + self.app.route('/comics/publisher/create', 'POST', self.post_create_publisher) + self.app.route('/comics/artist', 'GET', self.artist_list) + self.app.route('/comics/artist/', 'GET', self.artist_details) + self.app.route('/comics/artist/create', 'GET', self.get_artist_create) + self.app.route('/comics/artist/create', 'POST', self.post_create_artist) + self.app.route('/comics/storyarc', 'GET', self.storyarc_list) + self.app.route('/comics/storyarc/', 'GET', self.storyarc_details) + self.app.route('/comics/storyarc/create', 'GET', self.get_storyarc_create) + self.app.route('/comics/storyarc/create', 'POST', self.post_create_storyarc) + + def comic_index(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + return bottle.template('comic_index', dict(username=username)) + + + def comic_list(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + l = self.comics.get_comics() + return bottle.template('comic_list', dict(comics=l, username=username)) + + + def comic_details(self, id): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + comic = self.comics.get_comic(id) + errors = "" + if comic == None: + errors = "Entry not found" + return bottle.template('comic_template', dict(title=comic['title'], + id=comic['_id'], + current_order=comic['current_order'], + completed=comic['completed'], + errors="", + username=username)) + + + def get_comic_create(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + return bottle.template("comic_template", dict(title="", + id='newentry', + current_order=False, + completed=False, + errors="", + username=username)) + + + def post_create_comic(self): + comic_id = bottle.request.forms.get("id") + comic_title = bottle.request.forms.get("title") + comic_order = bottle.request.forms.get("current_order") + comic_completed = bottle.request.forms.get("completed") + if comic_id == "newentry": + self.comics.insert_entry(comic_title, None, comic_order, comic_completed) + else: + self.comics.update_entry(comic_id, comic_title, None, comic_order, comic_completed) + bottle.redirect("/comics/comic") + + + def publisher_list(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + l = self.publishers.get_publishers() + return bottle.template('publisher_list', dict(publishers=l, username=username)) + + + def publisher_details(self, id): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + publisher = self.publishers.get_publisher(id) + errors = "" + if publisher == None: + errors= "Entry not found" + return bottle.template('publisher_template', dict(name=publisher['name'], id=publisher['_id'], errors="", username=username)) + + + def get_publisher_create(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + return bottle.template("publisher_template", dict(name="", id='newentry', errors="", username=username)) + + + def post_create_publisher(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + publisher_id = bottle.request.forms.get("id") + publisher_name = bottle.request.forms.get("name") + if publisher_id == "newentry": + self.publishers.insert_entry(publisher_name) + else: + self.publishers.update_entry(publisher_id, publisher_name) + bottle.redirect("/comics/publisher") + + + def artist_list(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + l = self.artists.get_artists() + return bottle.template('artist_list', dict(artists=l, username=username)) + + + def artist_details(self, id): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + artist = self.artists.get_artist(id) + errors = "" + if artist == None: + errors= "Entry not found" + return bottle.template('artist_template', dict(name=artist['name'], id=artist['_id'], errors="", username=username)) + + + def get_artist_create(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + return bottle.template("artist_template", dict(name="", id='newentry', errors="", username=username)) + + + def post_create_artist(self): + artist_id = bottle.request.forms.get("id") + artist_name = bottle.request.forms.get("name") + if artist_id == "newentry": + self.artists.insert_entry(artist_name) + else: + self.artists.update_entry(artist_id, artist_name) + bottle.redirect("/comics/artist") + + + def storyarc_list(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + l = self.storyarcs.get_storyarcs() + return bottle.template('storyarc_list', dict(storyarcs=l, username=username)) + + + def storyarc_details(self, id): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + storyarc = self.storyarcs.get_storyarc(id) + errors = "" + if storyarc == None: + errors = "Entry not found" + return bottle.template('storyarc_template', dict(title=storyarc['title'], id=storyarc['_id'], errors="", username=username)) + + + def get_storyarc_create(self): + cookie = bottle.request.get_cookie("session") + username = self.sessions.get_username(cookie) + return bottle.template("storyarc_template", dict(title="", id='newentry', errors="", username=username)) + + + def post_create_storyarc(self): + storyarc_id = bottle.request.forms.get("id") + storyarc_title = bottle.request.forms.get("title") + if storyarc_id == "newentry": + self.storyarcs.insert_entry(storyarc_title) + else: + self.storyarcs.update_entry(storyarc_id, storyarc_title) + bottle.redirect("/comics/storyarc") diff --git a/bottle-docker/app/src/comics/artistDAO.py b/bottle-docker/app/src/comics/artistDAO.py new file mode 100644 index 0000000..3ef93b9 --- /dev/null +++ b/bottle-docker/app/src/comics/artistDAO.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from bson.objectid import ObjectId +import sys + +class ArtistDAO: + + # constructor for the class + def __init__(self, database): + self.db = database + self.artists = database.artists + + def get_artists(self): + cursor = self.artists.find() + l = [] + for artist in cursor: + l.append({'name':artist['name'], '_id':artist['_id']}) + return l + + def get_artist(self, artist_id): + artist = self.artists.find_one({"_id": ObjectId(artist_id)}) + return artist + + def insert_entry(self, artist_name): + print("inserting artist entry", artist_name) + + artist = {"name": artist_name} + + # now insert the post + try: + result = self.artists.insert_one(artist) + print("Matching artist: ", result.matched_count) + print("Modified artist: ", result.modified_count) + except: + print("Error inserting artist") + print("Unexpected error:", sys.exc_info()) + + def update_entry(self, artist_id, artist_name): + print("upserting artist entry", artist_name) + + filter_doc = {"_id": ObjectId(artist_id)} + artist = { "$set": {"name": artist_name}} + + # now insert the post + try: + result = self.artists.update_one(filter_doc, artist) + print("Modified artist: ", result.modified_count) + except: + print("Error inserting artist") + print("Unexpected error:", sys.exc_info()) diff --git a/bottle-docker/app/src/comics/comicDAO.py b/bottle-docker/app/src/comics/comicDAO.py new file mode 100644 index 0000000..82c122b --- /dev/null +++ b/bottle-docker/app/src/comics/comicDAO.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from bson.objectid import ObjectId +import sys + +class ComicDAO: + + # constructor for the class + def __init__(self, database): + self.db = database + self.comics = database.comics + + def get_comics(self): + cursor = self.comics.find() + l = [] + for comic in cursor: + l.append({'title':comic['title'], + '_id':comic['_id'], + 'current_order':comic['current_order'], + 'completed':comic['completed']}) + return l + + def get_comic(self, comic_id): + comic = self.comics.find_one({"_id": ObjectId(comic_id)}) + return comic + + def insert_entry(self, comic_title, comic_publisher=None, comic_order=False, comic_completed=False): + print("inserting comic entry", comic_title) + + comic = {"title": comic_title, "current_order": comic_order, "completed": comic_completed} + + # now insert the comic + try: + result = self.comics.insert_one(comic) + print("Modified comic: ", result.modified_count) + except: + print("Error inserting comic") + print("Unexpected error:", sys.exc_info()) + + def update_entry(self, comic_id, comic_title, comic_publisher=None, comic_order=False, comic_completed=False): + print("upserting comic entry", comic_title) + + filter_doc = {"_id": ObjectId(comic_id)} + comic = { "$set": {"title": comic_title, 'current_order': comic_order, 'completed': comic_completed}} + + # now insert the post + try: + result = self.comics.update_one(filter_doc, comic) + print("Matching comic: ", result.matched_count) + print("Modified comic: ", result.modified_count) + except: + print("Error inserting comic") + print("Unexpected error:", sys.exc_info()) \ No newline at end of file diff --git a/bottle-docker/app/src/comics/models.py b/bottle-docker/app/src/comics/models.py new file mode 100644 index 0000000..1cca3b1 --- /dev/null +++ b/bottle-docker/app/src/comics/models.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +from mongoengine import connect, Document +from mongoengine import StringField, BooleanField +from mongoengine import ListField, ReferenceField, GenericReferenceField +import pprint + + +class Publisher(Document): + name = StringField(required=True) + + def __str__(self): + s = "Publisher(%s)" % self.name + return s + + +class Artist(Document): + name = StringField(required=True) + className = StringField() + + def __str__(self): + s = "Artist(%s)" % self.name + return s + + +class Issue(Document): + number = StringField() + comic = GenericReferenceField() + is_read = BooleanField(default=False) + is_stock = BooleanField(default=False) + + def __str__(self): + s = "Issue(%s # %s, %s)" % (self.comic.title, self.number, self.is_read) + return s + + +class StoryArc(Document): + name = StringField(required=True) + comic = GenericReferenceField() + issues = ListField(ReferenceField(Issue)) + + def __str__(self): + s = "StoryArc(%s, %s)" % (self.name, self.comic.title) + return s + + +class Volume(Document): + name = StringField(required=True) + comic = GenericReferenceField() + issues = ListField(ReferenceField(Issue)) + + def __str__(self): + s = "Volume(%s, %s, %s)" % (self.id, self.name, self.comic.title) + return s + + +class Comic(Document): + title = StringField(required=True) + publisher = ReferenceField(Publisher) + current_order = BooleanField() + completed = BooleanField() + issues = ListField(ReferenceField(Issue)) + stories = ListField(ReferenceField(StoryArc)) + + def __str__(self): + if self.publisher is None: + s = "Comic(%s, %s, %s, %s)" % (self.title, self.publisher, self.current_order, self.completed) + return s + else: + s = "Comic(%s, %s, %s, %s)" % ( + self.title, self.publisher.name, self.current_order, self.completed) + return s + + +class TradePaperback(Document): + comic = ReferenceField(Comic) + issue_start = StringField() + issue_end = StringField() + + def __str__(self): + s = "TPB(%s)" % self.comic.title + return s + + +def get_publisher(name): + publisher = Publisher.objects(name=name) + if publisher.count() > 0: + return publisher[0] + else: + return None + + +def get_comic(title): + comic = Comic.objects(title=title) + if comic.count() > 0: + return comic[0] + else: + return None + + +def get_issue(title, number): + comic = get_comic(title) + issues = Issue.objects(number=number, comic=comic) + if issues.count() > 0: + return issues[0] + else: + return None + +if __name__ == '__main__': + connect('comics') + for publisher in Publisher.objects: + pprint.pprint(publisher) + for artist in Artist.objects: + pprint.pprint(artist) + for comic in Comic.objects: + pprint.pprint(comic) + for issue in Issue.objects: + pprint.pprint(issue) + for story in StoryArc.objects: + pprint.pprint(story) + for tpb in TradePaperback.objects: + pprint.pprint(tpb) diff --git a/bottle-docker/app/src/comics/publisherDAO.py b/bottle-docker/app/src/comics/publisherDAO.py new file mode 100644 index 0000000..f6a3cfe --- /dev/null +++ b/bottle-docker/app/src/comics/publisherDAO.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from bson.objectid import ObjectId +import sys + +class PublisherDAO: + + # constructor for the class + def __init__(self, database): + self.db = database + self.publishers = database.publishers + + def get_publishers(self): + cursor = self.publishers.find() + l = [] + for publisher in cursor: + l.append({'name':publisher['name'], '_id':publisher['_id']}) + return l + + def get_publisher(self, publisher_id): + publisher = self.publishers.find_one({"_id": ObjectId(publisher_id)}) + return publisher + + def insert_entry(self, publisher_name): + print("inserting publisher entry", publisher_name) + + publisher = {"name": publisher_name} + + # now insert the post + try: + result = self.publishers.insert_one(publisher) + print("Matching publisher: ", result.matched_count) + print("Modified publisher: ", result.modified_count) + except: + print("Error inserting publisher") + print("Unexpected error:", sys.exc_info()) + + def update_entry(self, publisher_id, publisher_name): + print("upserting publisher entry", publisher_name) + + filter_doc = {"_id": ObjectId(publisher_id)} + publisher = { "$set": {"name": publisher_name}} + + # now insert the post + try: + result = self.publishers.update_one(filter_doc, publisher) + print("Modified publisher: ", result.modified_count) + except: + print("Error inserting publisher") + print("Unexpected error:", sys.exc_info()) diff --git a/bottle-docker/app/src/comics/storyArcDAO.py b/bottle-docker/app/src/comics/storyArcDAO.py new file mode 100644 index 0000000..afe49a5 --- /dev/null +++ b/bottle-docker/app/src/comics/storyArcDAO.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from bson.objectid import ObjectId +import sys + + +class StoryArcDAO: + # constructor for the class + def __init__(self, database): + self.db = database + self.storyarcs = database.storyarcs + + def get_storyarcs(self): + cursor = self.storyarcs.find() + l = [] + for storyarc in cursor: + l.append({'title': storyarc['title'], '_id': storyarc['_id']}) + return l + + def get_storyarc(self, storyarc_id): + storyarc = self.storyarcs.find_one({"_id": ObjectId(storyarc_id)}) + return storyarc + + def insert_entry(self, storyarc_title): + print("inserting publisher entry", storyarc_title) + + storyarc = {"name": storyarc_title} + + # now insert the post + try: + result = self.storyarcs.insert_one(storyarc) + print("Matching storyarc: ", result.matched_count) + print("Modified storyarc: ", result.modified_count) + except: + print("Error inserting storyarc") + print("Unexpected error:", sys.exc_info()) + + def update_entry(self, storyarc_id, storyarc_title): + print("upserting storyarc entry", storyarc_title) + + filter_doc = {"_id": ObjectId(storyarc_id)} + storyarc = {"$set": {"name": storyarc_title}} + + # now insert the post + try: + result = self.storyarcs.update_one(filter_doc, storyarc) + print("Modified storyarc: ", result.modified_count) + except: + print("Error inserting storyarc") + print("Unexpected error:", sys.exc_info()) diff --git a/bottle-docker/app/src/kontor.css b/bottle-docker/app/src/kontor.css new file mode 100644 index 0000000..979f80f --- /dev/null +++ b/bottle-docker/app/src/kontor.css @@ -0,0 +1,89 @@ +body { +font-family: sans-serif; +color: #333333; +padding:4em 0 4em; +} +body, +.wrapper { +margin: 10px auto; +/*max-width: 60em;*/ +} + +header, nav, nav a, main, section, footer { +border-radius: 0px 0.5em 0.5em; +border: 1px solid; +padding: 10px; +margin: 10px; +} + +header { +position:fixed; +top:0px; +left:0px; +right:0px; +text-align:center; +padding:10px; +background: lightgrey; +/*border-bottom: 1px solid #d5d5d5;*/ +} + +nav { + position: fixed; + padding-top: 10em; + +font-size: 0.91em; +float: left; +width: 15em; +padding: 0; +background: lightskyblue; +border-color: skyblue; +} + +nav ul { +padding: 0; +} + +nav li { +list-style: none; +margin: 0; +padding: 0.1em; +} + +nav a { +display: block; +padding: 0.2em 10px; +font-weight: bold; +text-decoration: none; +background-color: skyblue; +color: #333; +} + +nav ul a:hover, +nav ul a:active { +color: #fffbf0; +background-color: #dfac20; +} + +main { +display: block; +background: lightblue; +border-color: #8a9da8; +margin-left: 15em; +min-width: 16em; /* Mindestbreite (der Überschrift) verhindert Anzeigefehler in modernen Browsern */ +} + +footer { +position:fixed; +padding: 10px; +margin-top: 10px; +bottom:0; +left: 0; +right:0; +background: lightgrey; +border-color: grey; +} + +footer p { +float:right; +margin: 0; +} diff --git a/bottle-docker/app/src/kontor.py b/bottle-docker/app/src/kontor.py new file mode 100644 index 0000000..e6309bc --- /dev/null +++ b/bottle-docker/app/src/kontor.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +import pymongo +import sessionDAO +import userDAO +import comics +#import library +import bottle +import cgi +import re + + +__author__ = 'tpeetz' + +app = bottle.Bottle() + + +def index(): + cookie = bottle.request.get_cookie("session") + username = sessions.get_username(cookie) + return bottle.template('kontor', dict(username=username)) + + +def show_signup(): + return bottle.template("signup", dict(username="", + password="", + password_error="", + email="", + username_error="", + email_error="", + verify_error ="")) + + +def process_signup(): + email = bottle.request.forms.get("email") + username = bottle.request.forms.get("username") + password = bottle.request.forms.get("password") + verify = bottle.request.forms.get("verify") + + # set these up in case we have an error case + errors = {'username': cgi.escape(username), 'email': cgi.escape(email)} + if validate_signup(username, password, verify, email, errors): + + if not users.add_user(username, password, email): + # this was a duplicate + errors['username_error'] = "Username already in use. Please choose another" + return bottle.template("signup", errors) + + session_id = sessions.start_session(username) + print(session_id) + bottle.response.set_cookie("session", session_id) + bottle.redirect("/welcome") + else: + print("user did not validate") + return bottle.template("signup", errors) + + +def show_login(): + return bottle.template('login', dict(username="", password="", login_error="")) + +def process_login(): + username = bottle.request.forms.get("username") + password = bottle.request.forms.get("password") + + print("user submitted ", username, "pass ", password) + + user_record = users.validate_login(username, password) + if user_record: + # username is stored in the user collection in the _id key + session_id = sessions.start_session(user_record['_id']) + + if session_id is None: + bottle.redirect("/internal_error") + + cookie = session_id + + # Warning, if you are running into a problem whereby the cookie being set here is + # not getting set on the redirect, you are probably using the experimental version of bottle (.12). + # revert to .11 to solve the problem. + bottle.response.set_cookie("session", cookie) + + bottle.redirect("/") + + else: + return bottle.template("login", dict(username=cgi.escape(username), password="", login_error="Invalid Login")) + + +def process_logout(): + cookie = bottle.request.get_cookie("session") + sessions.end_session(cookie) + bottle.response.set_cookie("session", "") + bottle.redirect("/") + + +def send_stylesheet(filename): + return bottle.static_file(filename, root='.', mimetype='text/css') + + +def setup_routing(app): + app.route('/', 'GET', index) + app.route('/signup', 'GET', show_signup) + app.route('/signup', 'POST', process_signup) + app.route('/login', 'GET', show_login) + app.route('/login', 'POST', process_login) + app.route('/logout', 'GET', process_logout) + app.route('/css/', 'GET', send_stylesheet) + + +# validates that the user information is valid for new signup, return True of False +# and fills in the error string if there is an issue +def validate_signup(username, password, verify, email, errors): + USER_RE = re.compile(r"^[a-zA-Z0-9_-]{3,20}$") + PASS_RE = re.compile(r"^.{3,20}$") + EMAIL_RE = re.compile(r"^[\S]+@[\S]+\.[\S]+$") + + errors['username_error'] = "" + errors['password_error'] = "" + errors['verify_error'] = "" + errors['email_error'] = "" + + if not USER_RE.match(username): + errors['username_error'] = "invalid username. try just letters and numbers" + return False + + if not PASS_RE.match(password): + errors['password_error'] = "invalid password." + return False + if password != verify: + errors['verify_error'] = "password must match" + return False + if email != "": + if not EMAIL_RE.match(email): + errors['email_error'] = "invalid email address" + return False + return True + + +setup_routing(app) + +connection = pymongo.MongoClient("mongodb://mongodb") +database = connection.kontor + +users = userDAO.UserDAO(database) +sessions = sessionDAO.SessionDAO(database) + +comics_plugin = comics.Plugin(app, database, sessions) +#library_plugin = library.Plugin(app, database, sessions) +print("starting app Kontor") +bottle.run(app, host="0.0.0.0", port=9000, debug=True) diff --git a/bottle-docker/app/src/sessionDAO.py b/bottle-docker/app/src/sessionDAO.py new file mode 100644 index 0000000..29a09cd --- /dev/null +++ b/bottle-docker/app/src/sessionDAO.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +import sys +import random +import string + + +# The session Data Access Object handles interactions with the sessions collection + +class SessionDAO: + + def __init__(self, database): + self.db = database + self.sessions = database.sessions + + # will start a new session id by adding a new document to the sessions collection + # returns the sessionID or None + def start_session(self, username): + + session_id = self.get_random_str(32) + session = {'username': username, '_id': session_id} + + try: + self.sessions.insert_one(session) + except: + print("Unexpected error on start_session:", sys.exc_info()[0]) + return None + + return str(session['_id']) + + # will send a new user session by deleting from sessions table + def end_session(self, session_id): + + if session_id is None: + return + + self.sessions.delete_one({'_id': session_id}) + + return + + # if there is a valid session, it is returned + def get_session(self, session_id): + + if session_id is None: + return None + + session = self.sessions.find_one({'_id': session_id}) + + return session + + # get the username of the current session, or None if the session is not valid + def get_username(self, session_id): + + session = self.get_session(session_id) + if session is None: + return None + else: + return session['username'] + + def get_random_str(self, num_chars): + random_string = "" + for i in range(num_chars): + random_string = random_string + random.choice(string.ascii_letters) + return random_string diff --git a/bottle-docker/app/src/userDAO.py b/bottle-docker/app/src/userDAO.py new file mode 100644 index 0000000..df5a107 --- /dev/null +++ b/bottle-docker/app/src/userDAO.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import hmac +import random +import string +import hashlib +import pymongo + + +# The User Data Access Object handles all interactions with the User collection. +class UserDAO: + + def __init__(self, db): + self.db = db + self.users = self.db.users + self.SECRET = 'verysecret' + + # makes a little salt + def make_salt(self): + salt = "" + for i in range(5): + salt = salt + random.choice(string.ascii_letters) + return salt + + # implement the function make_pw_hash(name, pw) that returns a hashed password + # of the format: + # HASH(pw + salt),salt + # use sha256 + + def make_pw_hash(self, pw,salt=None): + if salt == None: + salt = self.make_salt(); + return hashlib.sha256(pw + salt).hexdigest()+","+ salt + + # Validates a user login. Returns user record or None + def validate_login(self, username, password): + + user = None + try: + user = self.users.find_one({'_id': username}) + except: + print("Unable to query database for user") + + if user is None: + print("User not in database") + return None + + salt = user['password'].split(',')[1] + + if user['password'] != self.make_pw_hash(password, salt): + print("user password is not a match") + return None + + # Looks good + return user + + + # creates a new user in the users collection + def add_user(self, username, password, email): + password_hash = self.make_pw_hash(password) + + user = {'_id': username, 'password': password_hash} + if email != "": + user['email'] = email + + try: + self.users.insert_one(user) + except pymongo.errors.OperationFailure: + print("oops, mongo error") + return False + except pymongo.errors.DuplicateKeyError as e: + print("oops, username is already taken") + return False + + return True diff --git a/bottle-docker/app/src/views/artist_list.tpl b/bottle-docker/app/src/views/artist_list.tpl new file mode 100644 index 0000000..366d4b7 --- /dev/null +++ b/bottle-docker/app/src/views/artist_list.tpl @@ -0,0 +1,39 @@ + + + +Kontor + + + +
Kontor
+ +
+
+ +
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/artist_template.tpl b/bottle-docker/app/src/views/artist_template.tpl new file mode 100644 index 0000000..f981355 --- /dev/null +++ b/bottle-docker/app/src/views/artist_template.tpl @@ -0,0 +1,43 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+ {{errors}} +

Title

+ +
+

+ +

+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + + diff --git a/bottle-docker/app/src/views/comic_index.tpl b/bottle-docker/app/src/views/comic_index.tpl new file mode 100644 index 0000000..529a38e --- /dev/null +++ b/bottle-docker/app/src/views/comic_index.tpl @@ -0,0 +1,34 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/comic_list.tpl b/bottle-docker/app/src/views/comic_list.tpl new file mode 100644 index 0000000..a947073 --- /dev/null +++ b/bottle-docker/app/src/views/comic_list.tpl @@ -0,0 +1,44 @@ + + + +Kontor + + + +
Kontor
+ +
+
+ + + +%for comic in comics: + + + +%end + +
TitleCurrent OrderCompleted
{{comic['title']}}{{comic['current_order']}}{{comic['completed']}}
+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/comic_template.tpl b/bottle-docker/app/src/views/comic_template.tpl new file mode 100644 index 0000000..38f190e --- /dev/null +++ b/bottle-docker/app/src/views/comic_template.tpl @@ -0,0 +1,51 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+ {{errors}} +

Title

+ +
+

+ + +

+ +

+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/kontor.tpl b/bottle-docker/app/src/views/kontor.tpl new file mode 100644 index 0000000..260cb88 --- /dev/null +++ b/bottle-docker/app/src/views/kontor.tpl @@ -0,0 +1,27 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+
+%if (username == None): +
Login

Ingenieurbüro Thomas Peetz

+%end +%if (username != None): + +%end + + diff --git a/bottle-docker/app/src/views/login.tpl b/bottle-docker/app/src/views/login.tpl new file mode 100644 index 0000000..881a1af --- /dev/null +++ b/bottle-docker/app/src/views/login.tpl @@ -0,0 +1,52 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+ + + + + + + + + + + + + +
Username
Password{{login_error}}
+ + +
+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/publisher_list.tpl b/bottle-docker/app/src/views/publisher_list.tpl new file mode 100644 index 0000000..7139e17 --- /dev/null +++ b/bottle-docker/app/src/views/publisher_list.tpl @@ -0,0 +1,39 @@ + + + +Kontor + + + +
Kontor
+ +
+
+ +
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/publisher_template.tpl b/bottle-docker/app/src/views/publisher_template.tpl new file mode 100644 index 0000000..91cbc4a --- /dev/null +++ b/bottle-docker/app/src/views/publisher_template.tpl @@ -0,0 +1,42 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+ {{errors}} +

Title

+ +
+

+ +

+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/signup.tpl b/bottle-docker/app/src/views/signup.tpl new file mode 100644 index 0000000..2b3790c --- /dev/null +++ b/bottle-docker/app/src/views/signup.tpl @@ -0,0 +1,59 @@ + + + +Kontor + + + +
Kontor
+ +
+
+

Signup

+
+ + + + + + + + + + + + + + + + + + + + +
Username{{username_error}}
Password{{password_error}}
Verify Password{{verify_error}}
Email (optional){{email_error}}
+ +
+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/storyarc_list.tpl b/bottle-docker/app/src/views/storyarc_list.tpl new file mode 100644 index 0000000..82a5ce3 --- /dev/null +++ b/bottle-docker/app/src/views/storyarc_list.tpl @@ -0,0 +1,40 @@ + + + +Kontor + + + +
Kontor
+ +
+
+ +
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/app/src/views/storyarc_template.tpl b/bottle-docker/app/src/views/storyarc_template.tpl new file mode 100644 index 0000000..010d885 --- /dev/null +++ b/bottle-docker/app/src/views/storyarc_template.tpl @@ -0,0 +1,43 @@ + + + +Kontor + + + +
Kontor
+ +
+
+
+ {{errors}} +

Title

+ +
+

+ +

+
+
+
+%if (username == None): + Login +%end +%if (username != None): + {{username}} +%end +

Ingenieurbüro Thomas Peetz

+
+ + diff --git a/bottle-docker/docker-compose.yaml b/bottle-docker/docker-compose.yaml new file mode 100644 index 0000000..2469e59 --- /dev/null +++ b/bottle-docker/docker-compose.yaml @@ -0,0 +1,31 @@ +services: + mongodb: + image: mongo + restart: always + volumes: + - db-data:/var/lib/mongo + networks: + - backend-network + app: + build: app + restart: always + ports: + - 9000:9000 + networks: + - backend-network + - frontend-network + nginx: + image: nginx + restart: always + volumes: + - ./nginx:/etc/nginx/conf.d + - ./front-end:/var/www/front-end + ports: + - 8070:80 + networks: + - frontend-network +volumes: + db-data: +networks: + backend-network: + frontend-network: diff --git a/bottle-docker/front-end/index.html b/bottle-docker/front-end/index.html new file mode 100644 index 0000000..a9a660b --- /dev/null +++ b/bottle-docker/front-end/index.html @@ -0,0 +1,82 @@ + + + + Hello! + + + + + + +
+ +

Hello there!

+

Simple DEV environment setup with Docker and Docker Compose

+
+

Set url path(default is '/'), then query the app service.

+
+ + +
+

Server response

+
+ +
+
+
+ + + + + + diff --git a/bottle-docker/nginx/nginx.conf b/bottle-docker/nginx/nginx.conf new file mode 100644 index 0000000..7be3c45 --- /dev/null +++ b/bottle-docker/nginx/nginx.conf @@ -0,0 +1,14 @@ +server { + listen 80; + server_name localhost; + root /var/www/front-end; + + location /api { + proxy_pass http://app:9000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +