commit 59dd26765934ac321c31bf2a8cfbd86d22fd71c6 Author: irrlicht Date: Sat Dec 13 12:21:44 2025 +0100 initial 1312 funktioniert ein bisschen diff --git a/app.py b/app.py new file mode 100644 index 0000000..6f13c93 --- /dev/null +++ b/app.py @@ -0,0 +1,3 @@ +from flask import Flask +app = Flask(__name__) + diff --git a/auth.py b/auth.py new file mode 100644 index 0000000..04dea2a --- /dev/null +++ b/auth.py @@ -0,0 +1,190 @@ +from flask import render_template, request, make_response +import database, re, bcrypt, secrets, time + +from app import app + +def get_session(request): + sessionid = request.cookies.get('session') + + if not sessionid: + return False + + sessionid = str(sessionid) + sessioninfo = database.fetch("""SELECT uid, created_at, expires, description + FROM session + WHERE value=?;""", + [sessionid]) + + if len(sessioninfo) == 0: + return False + + if len(sessioninfo) > 1: + # there is a problem + return False + + return sessioninfo[0] + (sessionid,) + +@app.route("/auth/sessioninfo", methods=['GET']) +def sessioninfo(): + time.sleep(secrets.randbelow(100)/50) # Rate limiting + + sessioninfo = get_session(request) + + if not sessioninfo: + return "Invalid session" + + (uid, created_at, expires, description) = sessioninfo + # uid = sessioninfo[0][0] + # created_at = sessioninfo[0][1] + # expires = sessioninfo[0][2] + # description = sessioninfo[0][3] + return render_template("/auth/sessioninfo.html", + sessionid=sessionid, + uid=uid, + created_at=created_at, + expires=expires, + description=description) + +@app.route("/auth/logout", methods=['GET']) +def logout(): + sessioninfo = get_session(request) + + response = make_response("Erfolgreich abgemeldet") + response.set_cookie('session', value='', expires=0) + + if not sessioninfo: + return response + + database.execute("DELETE FROM session WHERE value = ?", [sessioninfo[4]]) + + return response + +@app.route("/auth/login", methods=['GET', 'POST']) +def login(): + sessioninfo = get_session(request) + + if sessioninfo: + uid = sessioninfo[0] + username = database.fetch("SELECT username FROM user WHERE uid = ?", [uid]) + username = username[0][0] + return render_template("auth/loginsuccess.html", username=username) + + + if request.method == 'GET': + return render_template("auth/login.html", + response="", + username="") + + username = _sanitizeUsername(request.form['user']) + if len(username) < 3: + return render_template("auth/login.html", + response=f"Der Nutzername {username} ist zu kurz.", + username=username) + + userquery = database.fetch("SELECT uid, password FROM user WHERE username=?", [username]) + + # STOP TIMING ATTACKS + time.sleep(secrets.randbelow(100)/50) + + if len(userquery) == 0: + return render_template("auth/login.html", + response=f"Der Nutzername >{username} oder das Passwort ist falsch.", + username=username) + + uidquery = userquery[0][0] + passquery = userquery[0][1] + + passform = str(request.form['pass']) + passform = passform.encode("utf8") + passcorrect = bcrypt.checkpw(passform, passquery) + + if not passcorrect: + return render_template("auth/login.html", + response=f"Der Nutzername {username} oder das >Passwort ist falsch.", + username=username) + + now = int(time.time()) + + timeout = int(request.form['timeout']) + timeout = now + timeout + + session = secrets.randbelow(999999999) + session = str(session) + str(now) + + database.execute("DELETE FROM session WHERE expires < ?", [str(now)]) + database.execute("""INSERT INTO session + (uid, created_at, expires, description, value) + VALUES (?, ?, ?, ?, ?);""", + (uidquery, int(time.time()), timeout, "", session) + ) + + response = make_response(render_template("auth/loginsuccess.html", username=username)) + response.set_cookie('session', value=session, expires=timeout) + return response + +@app.route("/auth/newuser", methods=['GET', 'POST']) +def newuser(): + if request.method == 'GET': + return render_template("auth/register.html", + responses=[], + username="") + + success = True + responses = [] + + username = _sanitizeUsername(request.form['user']) + if len(username) < 3: + success = False + responses.append(f"Der Nutzername {username} ist zu kurz.") + usernamequery = database.fetch("SELECT id FROM user WHERE username=?", [username]) + if len(usernamequery) != 0: + success = False + responses.append(f"Der Nutzername {username} wird bereits verwendet.") + + pass1 = str(request.form['pass1']) + pass2 = str(request.form['pass2']) + if pass1 != pass2: + success = False + responses.append(f"Passwort und Passwortbestätigung sind ungleich.") + + if len(pass1) < 10: + success = False + responses.append(f"Das Passwort ist kürzer als 10 Zeichen.") + + if not success: + return render_template("auth/register.html", + responses=responses, + username=username) + + uid = secrets.randbelow(99999999) + created_at = int(time.time()) + # emailcode = secrets.randbelow(999999) + passsalt = bcrypt.gensalt() + passhash = bcrypt.hashpw(pass1.encode("utf8"), passsalt) + + database.execute("""INSERT INTO user + (uid, created_at, username, password) VALUES + (?, ?, ?, ?)""", + (uid, created_at, username, passhash)) + + return render_template("auth/registersuccess.html", username=username) + +def _sanitizeUsername(username): + username = str(username) + username = username.strip() + username = username[:32] + + # Remove all non-word characters (everything except numbers and letters) + username = re.sub(r"[^._\w\s-]", '', username) + + # Replace all runs of whitespace with a single dash + username = re.sub(r"\s+", '_', username) + + return username + +def _verifyEmail(email): + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + if re.match(pattern, email): + return True + else: + return False diff --git a/database.py b/database.py new file mode 100644 index 0000000..f834a42 --- /dev/null +++ b/database.py @@ -0,0 +1,21 @@ +import sqlite3 + +def execute(query, data): + connection = sqlite3.connect("kver.db") + cursor = connection.cursor() + + cursor.execute(query, data) + + connection.commit() + connection.close() + +def fetch(query, data): + connection = sqlite3.connect("kver.db") + cursor = connection.cursor() + + result = cursor.execute(query, data) + result = result.fetchall() + + connection.close() + + return result diff --git a/dbcontroll.py b/dbcontroll.py new file mode 100644 index 0000000..84388c2 --- /dev/null +++ b/dbcontroll.py @@ -0,0 +1,31 @@ +import sqlite3 +from flask import render_template +from __main__ import app + +@app.route("/database") +def database(): + con = sqlite3.connect("kver.db") + cursor = con.cursor() + + # cursor.execute("INSERT INTO movie VALUES (?, ?, ?)", ("Erster Film", 1900, 8.2)) + # con.commit() + + res = cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + + return render_template("database.html", tables=res.fetchall()) + +# !!! USER VALUE IN SQL STATEMENT !!! +@app.route("/database/") +def db_table(table): + + con = sqlite3.connect("kver.db") + cursor = con.cursor() + + # cursor.execute("INSERT INTO movie VALUES (?, ?, ?)", ("Erster Film", 1900, 8.2)) + # con.commit() + + # columns = cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?;", [table]) + # !!! USER VALUE IN SQL-STATEMENT !!! + data = cursor.execute(f"SELECT * FROM {table};") + + return render_template("database_table.html", data=data.fetchall()) diff --git a/entry.py b/entry.py new file mode 100644 index 0000000..8956475 --- /dev/null +++ b/entry.py @@ -0,0 +1,113 @@ +from flask import render_template, request +from PIL import Image +# import database, re, bcrypt, secrets, time +import secrets, time, os, database +from auth import get_session +from app import app + +@app.route("/entry/feed/", methods=['GET']) +def feed(page): + page = int(page, 0) + page = max(0,page) + page = min(100,page) + + count = 20 + offset = page*count + + posts = database.fetch("SELECT * FROM entry ORDER BY created_at DESC LIMIT ? OFFSET ?", [count, offset]) + + posts_with_usernames = [] + for post in posts: + username = database.fetch("SELECT username FROM user WHERE uid = ?", [post[2]]) + posts_with_usernames.append(post + username[0]) + + return render_template("entry/feed.html", posts=posts_with_usernames) + +@app.route("/entry/create", methods=['GET', 'POST']) +def newentry(): + session = get_session(request) + + if not session: + return "Melde dich an, um einen Beitrag zu erstellen!" + + uid = session[0] + + if request.method == 'GET': + return render_template('entry/create.html') + + content = str(request.form["content"]) + content = content[:1000] + + fileattached = False + if 'file' in request.files: + file = request.files['file'] + fileattached=True + + if fileattached: + fileattached = _storeimage(file) + + if not fileattached and content == '': + return render_template('entry/create.html', responses=["Leerer Beitrag!"]) + + if not fileattached: + fileattached = "none" + + pid = secrets.randbelow(999999999999) + now = int(time.time()) + database.execute("""INSERT INTO entry + (pid, uid, created_at, content, filepath) + VALUES (?, ?, ?, ?, ?)""", + (pid, uid, now, content, fileattached)) + + return render_template('entry/create.html', responses=[content, str(fileattached)]) + +def _storeimage(file): + maxsize = 1024 + tmpfile = secrets.randbelow(999999) + tmppath = f"tmp/{tmpfile}" + + import resizegif + + try: + file.save(tmppath) + + i = Image.open(tmppath) + if i.format == 'GIF': + filetype = 'gif' + else: + i = i.convert("RGB") + filetype = 'jpg' + + newpath = f"static/media/{int(time.time())}-{tmpfile}.{filetype}" + + (width, height) = i.size; + aspect = width/height + + if width < maxsize and height < maxsize: + if filetype == 'gif': + resizegif.resize_gif(i, newpath, (width, height)) + else: + i.save(newpath) + + os.remove(tmppath) + return newpath + + if width > height: + width = maxsize + height = maxsize/aspect + else: + height = maxsize + width = aspect*maxsize + + if filetype == 'gif': + resizegif.resize_gif(i, newpath, (width, height)) + else: + i.thumbnail((width, height), Image.Resampling.LANCZOS) + i.save(newpath) + + os.remove(tmppath) + return newpath + except Exception as e: + print(e) + os.remove(tmppath) + return False diff --git a/imageupload.py b/imageupload.py new file mode 100644 index 0000000..d992988 --- /dev/null +++ b/imageupload.py @@ -0,0 +1,36 @@ +import os +from PIL import Image +from flask import request, render_template +from __main__ import app + +@app.route("/upload", methods=['GET', 'POST']) +def upload(): + state="Nothing done..." + success=False + + if request.method == 'POST': + if 'file' not in request.files: + state='No file' + + file = request.files['file'] + + if file.filename == '': + state="No File selected!" + + if file: + filename = "uploadedfile" + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + state="File uploaded!" + success=True + + imagedata = [] + if success: + try: + i = Image.open("uploads/uploadedfile") + imagedata.append(i.format) + imagedata.append(i.size) + except Exception as e: + imagedata.append(e) + + + return render_template("upload.html", state=state, imagedata=imagedata) diff --git a/main.py b/main.py new file mode 100644 index 0000000..c77709a --- /dev/null +++ b/main.py @@ -0,0 +1,38 @@ +import os +from flask import Flask, request, render_template +from datetime import datetime + +from werkzeug.exceptions import HTTPException + +from app import app +app.config['UPLOAD_FOLDER'] = "uploads/" + +@app.errorhandler(400) +def handle_bad_request(e): + print(f"400 BAD REQUEST: {e}") + return '400
BAD REQUEST' + +@app.template_filter('timestamp') +def timestamp_filter(value): + try: + dt = datetime.fromtimestamp(value) + return dt.strftime("%d.%m.%Y %H:%M") + except: + return value + +# import sendmail +import auth +# import dbcontroll +# import imageupload +import entry + +@app.route("/header") +def header(): + return render_template("header.html") + +@app.route("/") +def template(): + return render_template("index.html", name="Hmmm") + +if __name__ == "__main__": + app.run(debug=True) diff --git a/resizegif.py b/resizegif.py new file mode 100644 index 0000000..327f383 --- /dev/null +++ b/resizegif.py @@ -0,0 +1,44 @@ +# Source - https://stackoverflow.com/a +# Posted by brvoisin +# Retrieved 2025-12-13, License - CC BY-SA 4.0 + +""" +# Resize an animated GIF +Inspired from https://gist.github.com/skywodd/8b68bd9c7af048afcedcea3fb1807966 +Useful links: + * https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving + * https://stackoverflow.com/a/69850807 +Example: + ``` + python resize_gif.py input.gif output.gif 400,300 + ``` +""" + +import sys + +from PIL import Image +from PIL import ImageSequence + + +def resize_gif(input_image, output_path, max_size): + frames = list(_thumbnail_frames(input_image, max_size)) + output_image = frames[0] + output_image.save( + output_path, + save_all=True, + append_images=frames[1:], + disposal=input_image.disposal_method, + **input_image.info, + ) + + +def _thumbnail_frames(image, max_size): + for frame in ImageSequence.Iterator(image): + new_frame = frame.copy() + new_frame.thumbnail(max_size, Image.Resampling.LANCZOS) + yield new_frame + + +if __name__ == "__main__": + max_size = [int(px) for px in sys.argv[3].split(",")] # "150,100" -> (150, 100) + resize_gif(sys.argv[1], sys.argv[2], max_size) diff --git a/sendmail.py b/sendmail.py new file mode 100644 index 0000000..0a27766 --- /dev/null +++ b/sendmail.py @@ -0,0 +1,41 @@ +import smtplib, ssl, database, time + +def send(subject, message, recipient, type): + hour_ago = int(time.time()) - 3600 + count = database.fetch("SELECT COUNT(*) FROM email WHERE time > ?", [hour_ago]) + if count[0][0] > 5: + return False + + database.execute("INSERT INTO email (recipient, time, type) VALUES (?, ?, ?)", [ + recipient, + int(time.time()), + type + ]) + + smtp_server = "webspace29.do.de" + port = 587 # For starttls + sender_email = "system@bnd.wtf" + password = "" + message = f"""From: system@bnd.wtf\nSubject: {subject} + + {message}""" + + # Create a secure SSL context + context = ssl.create_default_context() + + # Try to log in to server and send email + try: + server = smtplib.SMTP(smtp_server,port) + server.ehlo() # Can be omitted + server.starttls(context=context) # Secure the connection + server.ehlo() # Can be omitted + server.login(sender_email, password) + server.sendmail(sender_email, recipient, message) + # TODO: Send email here + except Exception as e: + # Print any error messages to stdout + print(e) + finally: + server.quit() + + return True diff --git a/static/as_good_as_it_gets.jpg b/static/as_good_as_it_gets.jpg new file mode 100644 index 0000000..03c5baa Binary files /dev/null and b/static/as_good_as_it_gets.jpg differ diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 0000000..b4bbff2 --- /dev/null +++ b/static/css/main.css @@ -0,0 +1,110 @@ +html { + border: 1px solig gray; + font-family: "DejaVu Serif"; } + +div.entry { + border: 1px solid gray; + padding: 10px 15px; + margin: 0px; } + div.entry div.entrymeta { + display: flex; + justify-content: left; + align-items: center; + gap: 15px; + cursor: pointer; } + div.entry div.entrymeta span.time { + color: gray; } + div.entry div.entrymeta span.user { + text-decoration: underline; } + div.entry div.entrymeta img.userimage { + width: 80px; } + div.entry div.contents p { + margin: 8pt 0pt; } + div.entry img { + max-width: 100%; } + div.entry.comment { + border: none; + padding: 0px 15px; + margin: 0px; + display: flex; + gap: 5px; } + div.entry.comment .left { + width: 70px; + display: flex; + flex-direction: column; } + div.entry.comment .left .spacer { + background-color: black; + height: 100%; + width: 6%; + margin: 0px 47%; } + +form { + display: flex; + flex-direction: column; + width: 400px; + max-width: 100%; } + form textarea { + resize: vertical; + font-size: 1.0rem; } + form.create { + border: 1px solid gray; + padding: 10px 15px; + margin: 0px; } + form hr { + width: 100%; } + +input, +textarea, +button, +select { + padding: 4pt 6pt; + margin: 3pt 0pt; + border: 2px solid black; + cursor: pointer; + box-shadow: 0px 0px; } + input:hover, + textarea:hover, + button:hover, + select:hover { + box-shadow: 1px 1px; } + input:focus, + textarea:focus, + button:focus, + select:focus { + box-shadow: 3px 3px black; + outline: none; } + +button { + background-color: #ffabcd; } + +#response div { + padding: 4pt 6pt; } + +#response .error { + color: red; } + +.loading { + background-color: #fffa; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-content: center; + transition: background-color 5s; + transition-timing-function: ease-in; } + +.post { + border: 1px solid black; + padding: 15px 15px; + margin: 10px 0px; } + .post img { + max-width: 100%; + max-height: 800px; } + .post .meta { + color: gray; } + .post .content { + margin: 10px 0px; } diff --git a/static/css/main.scss b/static/css/main.scss new file mode 100644 index 0000000..a54e832 --- /dev/null +++ b/static/css/main.scss @@ -0,0 +1,178 @@ +// @font-face { +// font-family: 'avenir'; +// src: url('/static/avenir-font/AvenirLTStd-Medium.otf'); +// font-weight: normal; +// font-style: normal; +// } + +html { + + // font-family: "avenir"; + // font-weight: 400; + // font-style: normal; + + // background-color: #e9e9ed; + border: 1px solig gray; + // border-radius: 5px; + font-family: "DejaVu Serif"; +} + +div.entry { + // background-color: green; + border: 1px solid gray; + padding: 10px 15px; + margin: 0px; + + div.entrymeta { + display: flex; + justify-content: left; + align-items: center; + gap: 15px; + cursor: pointer; + + span.time { + // background-color: red; + color: gray; + } + + span.user { + // background-color: blue; + text-decoration: underline; + } + + img.userimage { + width: 80px; + } + } + + div.contents { + // background-color: yellow; + + p { + // background-color: orange; + margin: 8pt 0pt; + } + } + + img { + max-width: 100%; + } + + &.comment { + border: none; + padding: 0px 15px; + margin: 0px; + + display: flex; + gap: 5px; + + .left { + width: 70px; + display: flex; + flex-direction: column; + // background-color: green; + + .spacer { + background-color: black; + height: 100%; + width: 6%; + margin: 0px 47%; + } + } + // .right { + // background-color: red; + // } + // .contents { + // display: flex; + // } + } +} + +form { + display: flex; + flex-direction: column; + width: 400px; + max-width: 100%; + + textarea { + // width: 100%; + resize: vertical; + font-size: 1.0rem; + } + + &.create { + border: 1px solid gray; + padding: 10px 15px; + margin: 0px; + } + + hr { + width: 100%; + } +} + +input, +textarea, +button, +select { + padding: 4pt 6pt; + margin: 3pt 0pt; + border: 2px solid black; + cursor: pointer; + box-shadow: 0px 0px; + + &:hover { + box-shadow: 1px 1px; + } + + &:focus { + box-shadow: 3px 3px black; + outline: none; + } +} + +button { + background-color: #ffabcd; +} + +#response { + div { + padding: 4pt 6pt; + } + + .error { + color: red; + } +} + +.loading { + // background-image: url('/static/spinner.gif'); + background-color: #fffa; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-content: center; + transition: background-color 5s; + transition-timing-function: ease-in; +} + +.post { + border: 1px solid black; + padding: 15px 15px; + margin: 10px 0px; + img { + max-width: 100%; + max-height: 800px; + } + .meta { + color: gray; + } + .content { + margin: 10px 0px; + } +} diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..fda02e8 --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,3 @@ +$(() => { + $('#auth-visible').hide() +}) diff --git a/static/spinner.gif b/static/spinner.gif new file mode 100644 index 0000000..16d25c0 Binary files /dev/null and b/static/spinner.gif differ diff --git a/templates/auth/login.html b/templates/auth/login.html new file mode 100644 index 0000000..8a63bb7 --- /dev/null +++ b/templates/auth/login.html @@ -0,0 +1,17 @@ +

Einloggen

+
+
+
+ + + +
+ {{response}} +
+ diff --git a/templates/auth/loginsuccess.html b/templates/auth/loginsuccess.html new file mode 100644 index 0000000..31e25d4 --- /dev/null +++ b/templates/auth/loginsuccess.html @@ -0,0 +1,3 @@ +

Willkommen, {{username}}!

+ + diff --git a/templates/auth/register.html b/templates/auth/register.html new file mode 100644 index 0000000..d1cdc7e --- /dev/null +++ b/templates/auth/register.html @@ -0,0 +1,15 @@ +

Neuen Account registrieren

+
+
+
+
+ + + +
+{% for r in responses %} +{{ r }}
+{% endfor %} +
+ + diff --git a/templates/auth/registersuccess.html b/templates/auth/registersuccess.html new file mode 100644 index 0000000..82b8752 --- /dev/null +++ b/templates/auth/registersuccess.html @@ -0,0 +1 @@ +

Willkommen im Kontrollverlust!

diff --git a/templates/auth/sessioninfo.html b/templates/auth/sessioninfo.html new file mode 100644 index 0000000..4ea03ff --- /dev/null +++ b/templates/auth/sessioninfo.html @@ -0,0 +1,5 @@ +Sessionid: {{ sessionid }}
+UID: {{ uid }}
+Created: {{ created_at | timestamp }}
+Expires: {{ expires | timestamp }}
+Description: {{ description }}
diff --git a/templates/baseof.html b/templates/baseof.html new file mode 100644 index 0000000..a4518ea --- /dev/null +++ b/templates/baseof.html @@ -0,0 +1,31 @@ + + + + + + + {% block title %}{% endblock %} + + + + + + + Impressum + Datenschutz + Kontakt +
+

Kontrollverlust

+ Verbuggte scheise hier. Komm hack mich doch, trau dich! +
+ {% block content %}{% endblock %} + + diff --git a/templates/database.html b/templates/database.html new file mode 100644 index 0000000..8057342 --- /dev/null +++ b/templates/database.html @@ -0,0 +1,15 @@ + + + + + + + Database + + + +{% for tablename in tables %} + {{ tablename[0] }}
+{% endfor %} + + diff --git a/templates/database_table.html b/templates/database_table.html new file mode 100644 index 0000000..873a446 --- /dev/null +++ b/templates/database_table.html @@ -0,0 +1,9 @@ +
+ {% for row in data %} + + {% for column in row %} + + {% endfor %} + + {% endfor %} +
{{ column }}
diff --git a/templates/entry/create.html b/templates/entry/create.html new file mode 100644 index 0000000..dc59b69 --- /dev/null +++ b/templates/entry/create.html @@ -0,0 +1,11 @@ +
+ + + + +
+{% for r in responses %} +{{ r }}
+{% endfor %} +
+
diff --git a/templates/entry/feed.html b/templates/entry/feed.html new file mode 100644 index 0000000..afc9a73 --- /dev/null +++ b/templates/entry/feed.html @@ -0,0 +1,15 @@ +{% for p in posts %} + +
+
+ {{ p[6] }} - {{ p[3] | timestamp }}
+
+
+ {{ p[4] }} +
+{% if p[5] != 'none' %} + +{% endif %} +
+ +{% endfor %} diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..46a3c69 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,6 @@ + + +
+
+ +
diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..ac3861e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,15 @@ +{% extends "baseof.html" %} + +{% block title %}Index{% endblock %} + +{% block content %} + +
+ +
+ +
+ +
+ +{% endblock %} diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 0000000..c0d0722 --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,22 @@ + + + + + + + My new Site! + + + +

Upload new File

+ {{ state }} +
+ + +
+ + {% for id in imagedata %} + {{ id }} + {% endfor %} + + diff --git a/uploads/as_good_as_it_gets.jpg b/uploads/as_good_as_it_gets.jpg new file mode 100644 index 0000000..03c5baa Binary files /dev/null and b/uploads/as_good_as_it_gets.jpg differ diff --git a/uploads/uploadedfile b/uploads/uploadedfile new file mode 100644 index 0000000..603b625 Binary files /dev/null and b/uploads/uploadedfile differ