move Git repository kontor-docker to directory bottle-docker

This commit is contained in:
Thomas Peetz
2025-03-29 19:05:31 +01:00
parent 45971518ee
commit a5cdf8867a
29 changed files with 1681 additions and 0 deletions
+129
View File
@@ -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/
+2
View File
@@ -0,0 +1,2 @@
# kontor-bottle
Kontor with Python Bottle Framework
+19
View File
@@ -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" ]
+2
View File
@@ -0,0 +1,2 @@
pymongo
bottle
+193
View File
@@ -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/<id>', '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/<id>', '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/<id>', '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/<id>', '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")
+49
View File
@@ -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())
+52
View File
@@ -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())
+121
View File
@@ -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)
@@ -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())
@@ -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())
+89
View File
@@ -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;
}
+148
View File
@@ -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/<filename:re:.*\.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)
+64
View File
@@ -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
+75
View File
@@ -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
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for artist in artists:
<li><a href="/comics/artist/{{artist['_id']}}">{{artist['name']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/artist/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="name" size="60" value="{{name}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<table border="1">
<tr><td>Title</td><td>Current Order</td><td>Completed</td></tr>
<tr>
%for comic in comics:
<td><a href="/comics/comic/{{comic['_id']}}">{{comic['title']}}</a></td>
<td>{{comic['current_order']}}</td>
<td>{{comic['completed']}}</td>
%end
</tr>
</table>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/comic/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="title" size="60" value="{{title}}"><br>
<p>
<label>
<input type="checkbox" name="current_order" size="60" value="{{current_order}}">
Current Order
</label>
<label>
<input type="checkbox" name="completed" size="60" value="{{completed}}">
Completed
</label>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
</details>
</main>
%if (username == None):
<footer><a href="/login">Login</a><p>Ingenieurb&uuml;ro Thomas Peetz</p></footer>
%end
%if (username != None):
<footer><a href="/logout">{{username}}</a><p>Ingenieurb&uuml;ro Thomas Peetz</p></footer>
%end
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form method="POST">
<table>
<tr>
<td class="label">Username</td>
<td><input type="text" name="username" value="{{username}}"></td>
<td class="error"></td>
</tr>
<tr>
<td class="label">Password</td>
<td><input type="password" name="password" value=""></td>
<td class="error">{{login_error}}</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for publisher in publishers:
<li><a href="/comics/publisher/{{publisher['_id']}}">{{publisher['name']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics/comic">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/publisher/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="name" size="60" value="{{name}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<h2>Signup</h2>
<form method="post">
<table>
<tr><td class="label">Username</td>
<td><input type="text" name="username" value="{{username}}"></td>
<td class="error">{{username_error}}</td>
</tr>
<tr>
<td class="label">Password</td>
<td><input type="password" name="password" value=""></td>
<td class="error">{{password_error}}</td>
</tr>
<tr>
<td class="label">Verify Password</td>
<td><input type="password" name="verify" value=""></td>
<td class="error">{{verify_error}}</td>
</tr>
<tr>
<td class="label">Email (optional)</td>
<td><input type="text" name="email" value="{{email}}"></td>
<td class="error">{{email_error}}</td>
</tr>
</table>
<input type="submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics/comic">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
<li><a href="/comics/storyarc">StoryArcs</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for storyarc in storyarcs:
<li><a href="/comics/storyarc/{{storyarc['_id']}}">{{storyarc['title']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
<li><a href="/comics/storyarc">StoryArcs</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/storyarc/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="title" size="60" value="{{title}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+31
View File
@@ -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:
+82
View File
@@ -0,0 +1,82 @@
<html>
<head>
<title>Hello!</title>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<style>
*, ::after, ::before {
box-sizing: inherit;
background-color: #9eb5c2;
color: rgb(8, 8, 71);
}
.moby {
padding: 2rem 1rem;
margin-bottom: 2rem;
border-radius: .3rem;
border-bottom: 0;
text-align: center;
align-content: center;
align-items: center;
padding: 4rem 2rem;
}
.moby button {
color: #fff;
background-color: rgb(6, 105, 138);
border-color: rgb(6, 105, 138);
padding: 5px;
border-radius: 5px;
margin: 5px;
}
.moby .query {
text-align: left;
border: 3px dotted #e9ecef;
padding: 1.5rem 1.2rem;
max-width: 800px;
min-height: 200px;
margin-left: auto;
margin-right: auto;
border-radius: .3rem;
}
.moby .query button:hover{
cursor: pointer;
background-color: #73abff;
}
</style>
</head>
<body>
<div class="moby">
<img src="https://www.docker.com/sites/default/files/Whale%20Logo332_5.png" />
<h1>Hello there!</h1>
<p>Simple DEV environment setup with Docker and Docker Compose</p>
<div class="query">
<p>Set url path(default is '/'), then query the app service.</p>
<div>
<button type="submit" onclick="queryServer()">GET</button>
<input id="path" type="text">
</div>
<h3>Server response</h3>
<div id="response">
</div>
</div>
</div>
<script type="text/javascript">
function queryServer() {
const Http = new XMLHttpRequest();
const path = document.getElementById('path').value;
const url = "/";
Http.open("GET", url + path);
Http.send();
Http.onreadystatechange = (e) => {
document.getElementById('response').innerHTML = Http.responseText;
}
}
</script>
</body>
</html>
+14
View File
@@ -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;
}
}