From 93d419d8e7b9abbfee4206d36a52e0627be7b932 Mon Sep 17 00:00:00 2001 From: Dan Muckerman Date: Sat, 16 Jan 2021 23:55:36 -0500 Subject: [PATCH] Add images and make profile page functional. Add 404 pages for links that don't resolve. --- .gitignore | 3 +- app.py | 124 ++++++++++++++++++++++++++++++---- links/links.db | Bin 32768 -> 32768 bytes static/style.css | 11 +++ templates/404.j2 | 29 ++++++++ templates/fragments/navbar.j2 | 7 +- templates/fragments/table.j2 | 10 +-- templates/image.j2 | 44 ++++++++++++ templates/link.j2 | 2 +- templates/paste.j2 | 2 +- templates/profile.j2 | 40 +++++++++-- templates/text.html | 6 -- 12 files changed, 245 insertions(+), 33 deletions(-) create mode 100644 templates/404.j2 create mode 100644 templates/image.j2 delete mode 100644 templates/text.html diff --git a/.gitignore b/.gitignore index cb7bddf..ab9e2aa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ venv.bak/ users.db -node_modules/ \ No newline at end of file +node_modules/ +links/images/* \ No newline at end of file diff --git a/app.py b/app.py index 83f050c..8075d07 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,4 @@ +from flask.helpers import send_from_directory import ldap as l from ldap3 import Server, Connection from ldap3.core.exceptions import LDAPBindError @@ -9,6 +10,7 @@ from flask_wtf import FlaskForm from flask_cache_buster import CacheBuster from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import DataRequired +from werkzeug.utils import secure_filename from flask_bootstrap import Bootstrap import short_url import os @@ -30,6 +32,9 @@ app.config['LDAP_BASE_DN'] = os.environ.get('LDAP_BASE_DN') app.config['LDAP_USERNAME'] = os.environ.get('LDAP_USERNAME') app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD') +# Uploads +app.config['UPLOAD_FOLDER'] = 'links/images' + # OpenLDAP app.config['LDAP_OBJECTS_DN'] = 'dn' app.config['LDAP_OPENLDAP'] = True @@ -40,6 +45,7 @@ app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=posixAccount)(uid=%s))' #app.config['REMEMBER_COOKIE_DOMAIN'] = os.environ.get('COOKIE_DOMAIN') short_domain = os.environ.get('SHORT_DOMAIN') +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} db = SQLAlchemy(app) @@ -132,7 +138,8 @@ def get_current_user(): def index(): pastes = get_pastes_for_user() links = get_links_for_user() - return render_template('profile.j2', user = current_user.get_user_dict(), short_domain = short_domain, links = links, pastes = pastes, images = []) + images = get_images_for_user() + return render_template('profile.j2', user = current_user.get_user_dict(), short_domain = short_domain, links = links, pastes = pastes, images = images) @app.route('/link') @@ -147,6 +154,37 @@ def text(): return render_template('paste.j2', user = current_user.get_user_dict()) +@app.route('/image', methods=['GET', 'POST']) +@login_required +def image(): + if request.method == 'POST': + print(request.files) + # check if the post request has the file part + if 'file' not in request.files: + return render_template('image.j2', user = current_user.get_user_dict(), short_domain = short_domain, error_msg = "No file part.") + file = request.files['file'] + # if user does not select file, browser also + # submit an empty part without filename + if file.filename == '': + return render_template('image.j2', user = current_user.get_user_dict(), short_domain = short_domain, error_msg = "No selected file.") + if file and allowed_file(file.filename): + conn = sqlite3.connect('links/links.db') + c = conn.cursor() + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + + c.execute("INSERT INTO images (filename, user_id) VALUES (?, ?)", (filename, current_user.get_user_dict()['dn'])) + + c.execute("SELECT * FROM images WHERE filename=?", (filename,)) + row = c.fetchone() + print(row[0]) + conn.commit() + conn.close() + url_fragment = short_url.encode_url(row[0]) + return render_template('image.j2', user = current_user.get_user_dict(), short_domain = short_domain, success_msg = "Your image link is {}/i/{}".format(short_domain, url_fragment, short_domain, url_fragment)) + return render_template('image.j2', user = current_user.get_user_dict(), short_domain = short_domain) + + @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: @@ -207,7 +245,7 @@ def shorten_url(): conn.commit() conn.close() url_fragment = short_url.encode_url(row[0]) - return "Your shortened link is {}/l/{}".format(short_domain, url_fragment, short_domain, url_fragment) + return "Your shortened link is {}/l/{}".format(short_domain, url_fragment, short_domain, url_fragment) conn.commit() conn.close() return 'Error' @@ -230,21 +268,46 @@ def save_paste(): conn.commit() conn.close() url_fragment = short_url.encode_url(row[0]) - return {"success": True, "msg": "Your paste link is {}/p/{}".format(short_domain, url_fragment, short_domain, url_fragment)} + return {"success": True, "msg": "Your paste link is {}/p/{}".format(short_domain, url_fragment, short_domain, url_fragment)} conn.commit() conn.close() return {'success': False} +@app.route('/delete', methods=['POST']) +@login_required +def delete(): + if request.method == 'POST': + data = request.json + table = data['table'] + conn = sqlite3.connect('links/links.db') + c = conn.cursor() + if table == 'links': + c.execute("DELETE FROM links WHERE id=? AND user_id=?", (data['id'], current_user.get_user_dict()['dn'])) + elif table == 'pastes': + c.execute("DELETE FROM pastes WHERE id=? AND user_id=?", (data['id'], current_user.get_user_dict()['dn'])) + elif table == 'images': + c.execute("DELETE FROM images WHERE id=? AND user_id=?", (data['id'], current_user.get_user_dict()['dn'])) + else: + return {'success': False, 'msg': 'This table doesn\'t exist!'} + conn.commit() + conn.close() + return {'success': True, 'msg': 'Deleted successfully!'} + return {'success': False, 'msg': 'An error occurred.'} + + @app.route('/l/') def expand_url(url): idx = short_url.decode_url(url) conn = sqlite3.connect('links/links.db') c = conn.cursor() c.execute("SELECT * FROM links WHERE id=?", (idx,)) - out_link = c.fetchone()[1] - - return redirect(out_link) + out = c.fetchone() + + if out != None: + out_link = out[1] + return redirect(out_link) + return render_template('404.j2') @app.route('/p/') @@ -253,10 +316,26 @@ def show_paste(url): conn = sqlite3.connect('links/links.db') c = conn.cursor() c.execute("SELECT * FROM pastes WHERE id=?", (idx,)) - out_paste = str(c.fetchone()[1], 'utf-8') + out = c.fetchone() + + if out != None: + out_paste = str(out[1], 'utf-8') + return render_template('public_paste.j2', paste = out_paste) + return render_template('404.j2') - return render_template('public_paste.j2', paste = out_paste) +@app.route('/i/') +def show_image(url): + idx = short_url.decode_url(url) + conn = sqlite3.connect('links/links.db') + c = conn.cursor() + c.execute("SELECT * FROM images WHERE id=?", (idx,)) + out = c.fetchone() + + if out != None: + filename = out[1] + return send_from_directory(app.config['UPLOAD_FOLDER'], filename=filename, as_attachment=False) + return render_template('404.j2') def get_pastes_for_user(): conn = sqlite3.connect('links/links.db') @@ -265,8 +344,9 @@ def get_pastes_for_user(): out = [] for row in c.fetchall(): - a = "{}/p/{}".format(short_domain, short_url.encode_url(row[0])) - out.append((row[0], a)) + a = "{}/p/{} - {}".format(short_domain, short_url.encode_url(row[0]), str(row[1], 'utf-8')[:80]) + b = "{}/p/{}".format(short_domain, short_url.encode_url(row[0])) + out.append((row[0], a, b)) return out @@ -278,13 +358,31 @@ def get_links_for_user(): out = [] for row in c.fetchall(): - print(row) - a = "{}/l/{}".format(short_domain, short_url.encode_url(row[0])) - out.append((row[0], a)) + a = "{}/l/{} - {}".format(short_domain, short_url.encode_url(row[0]), row[1]) + b = "{}/l/{}".format(short_domain, short_url.encode_url(row[0])) + out.append((row[0], a, b)) + + return out + + +def get_images_for_user(): + conn = sqlite3.connect('links/links.db') + c = conn.cursor() + c.execute("SELECT * FROM images WHERE user_id=?", (current_user.get_user_dict()["dn"],)) + + out = [] + for row in c.fetchall(): + a = "{}/i/{}".format(short_domain, short_url.encode_url(row[0])) + out.append((row[0], a, a)) return out +def allowed_file(filename): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + @app.route('/logout') @login_required def logout(): diff --git a/links/links.db b/links/links.db index 147aa741d545848b13031bf9d827de1288b0b91b..45a32710552333134dc6a81a5289d3f4d87b4d23 100644 GIT binary patch delta 387 zcmX|*%}WA77>D1TUE2?xd5y@BjSz_lib6U^T6iil!edYyY@uinTBi8n`?Ol5)grDrnF0V!-0`8_@|u}~m2H<|2~?T~ z_RH${PCv0j^VsM~$xec2>D;O_ZKyNpv~(S7TE{iJQnya+MwDc0zgQ?2Gv&hOPBD`W zA%9{E>Pe+$pILRDW9z*ydMPRR4D+6oAxm-oinCME_rqK?Wg(^U7d$X%pqB)|Te1HJ zr3=qFH;geH2{=Slf>$#30f#uizJPBS*A+RfM|FI#VH$+Bfwj>U0xbVX6*S?&b9gXq z&#`!W_bP1XZmDSy^Z`fc<9Cnkec+<#p9q#hB1{o6S&|5mWnCXDYE`Y;b&fQm-glil QHSurhqc411Y?g=JkQR3r!KU$A4o=Dx= zToH!DzEoXi;3MaKr%-Ud1aKT@0@E0#5~d=i_b`oMda@Ose0j}&!fD)mQ1RBCa^;aT z@%Kd6`=#>8`^mfG-FZzdIz!>R;*9R@m25XU1l33c5g-CYfCvx)B0vO)01+SpM1Tko0U~fV z2-sM@jq%e)1c(3;AOb{y2oM1xKm>>Y5g-CYfC#*U1S)7#(fiHu{=|$nB0vO)01+Sp zM1Tko0U|&IhyW2F0z}}968JFJ+bxwQ3;Fzj;&`!CE#wm~D&7>%&41`j{OCQXJe*kY zE}l^{No^uP1c(3;AOb{y2oM1xKm>@uJ3(M@zBK9#rUp|TV`4o&J9{8ZCuui)*_~x6 z*Rsp&nuq%xiPQXpR}@{qiSl*bk;?FhEA2-MH&_I#iGRd#;kRf83+!zE10|DqMkd*T zo(cQ~!~ISqe1`uPv9XwAeV^MCeweiVk+So7t`zDdeIpHzty1%!&0YI+NR6b0vg4mx zo~CJ|FB6lrd1ijfjNiH5TL?DOkJD@49^Cdfhtx3sa_o|K*>+GaJ4;V34J87xtmi+O zo12?Gz}f%9LK+pz&gQALuf*TT*bk=*T{N%k{pxssd5^t6yx;LFZA5?w5CI}U1c(3; zAOb{y2oM1xKm>@u86;3H6$<6D%|}LTUMktVSS*#s$Ft}E#}1v!7nspT1c(3;AOb{y z2oQmx61cxv9Cdy;KbX1z%Mnuowm1t|Ti~k8eb;Suga-UDNEM4?u7F`IDhgngD?d?v z*kB3+RKT$4nTm}=4Mu_=atvohJVgP@2t3sGl}33(jkT;?LyYG@?t@Ge#3JSx)8Y-+ zydFYv!X8&>R9^oEk3-qFHBfpWZLkj7 zi3kIz*W(1$sMl2&>ibZ;k)7NZA+JH*pcV+Yu1*2ZhYZ*MHc)HStZ3>1>Vg0DJusvY z@%PsYqt5-<`m~l{&}!Xs-R3cRFJW(57TS``5iM%HKUmLp*V#^3L8FdYs$X?JCipog1x_wX@*(*j810Pk{i3IeNG-@wsx zYSH6(%b$3-Ohnu_9dtGUk2$U;ANeAag&y&V_ky|th3nu8#jPE>VU7*TSdww(02QDH zJVbvTq3^Xy1$ZS!|5d*MnIlgbx@E=CzXpIUUA=nLxd-Msd-bWkC*m6JGYsrl5K0!H zfSeWsbkVpze;|CcSwhOeN1t!l5xVK1#}M}cx8B|A!;ghFmT|236Fp9Jj+N{QE4JOw zVKD5ji#Tp12t`5KK_3cTRNY{*BJ5)i%!quTf)pRT$m*GtW77eSH+~1-8{nHnVFyXz zk$v9+!i}WJy4lvi=M`|sz?TYWfks%VCwkehR^guX5vjsa1jmeAFC`CADt7Wocj>Y5g-CYfCvx)B0vP*GJ(_h F|36+FGyDJm diff --git a/static/style.css b/static/style.css index e2d2e57..ce70587 100644 --- a/static/style.css +++ b/static/style.css @@ -139,4 +139,15 @@ top: 0; left: 0; overflow: hidden; +} + +.faded { + color: white; + opacity: 0.25; +} + +.clip-row { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } \ No newline at end of file diff --git a/templates/404.j2 b/templates/404.j2 new file mode 100644 index 0000000..1f91112 --- /dev/null +++ b/templates/404.j2 @@ -0,0 +1,29 @@ +{% extends "bootstrap/base.html" %} +{% block title %}tia.paste{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+
+
+

404

+

This page doesn't exist! How did you get here?

+
+
+
+{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/templates/fragments/navbar.j2 b/templates/fragments/navbar.j2 index fa5f52e..4b18ace 100644 --- a/templates/fragments/navbar.j2 +++ b/templates/fragments/navbar.j2 @@ -18,8 +18,13 @@ Paste {% endif %} + {% if request.path == url_for('image') %} + {% endif %} diff --git a/templates/fragments/table.j2 b/templates/fragments/table.j2 index c598293..db5a4c0 100644 --- a/templates/fragments/table.j2 +++ b/templates/fragments/table.j2 @@ -1,11 +1,11 @@ -{% macro build(links_list) %} - +{% macro build(links_list, table) %} +
{% if links_list is defined %} {% for link in links_list %} - - - + + + {% endfor %} {% endif %} diff --git a/templates/image.j2 b/templates/image.j2 new file mode 100644 index 0000000..fbfe840 --- /dev/null +++ b/templates/image.j2 @@ -0,0 +1,44 @@ +{% extends "bootstrap/base.html" %} +{% block title %}tia.paste{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} +{% include "fragments/navbar.j2" %} +{% endblock %} + +{% block content %} + + +
+ {% if success_msg %} + + {% endif %} + +
+
+
+ +
+ +

+ +
+
+
+ +
+{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/templates/link.j2 b/templates/link.j2 index 1effe6a..cd3a507 100644 --- a/templates/link.j2 +++ b/templates/link.j2 @@ -23,7 +23,7 @@
- +
diff --git a/templates/paste.j2 b/templates/paste.j2 index 4b52890..e754e33 100644 --- a/templates/paste.j2 +++ b/templates/paste.j2 @@ -19,7 +19,7 @@
- +

diff --git a/templates/profile.j2 b/templates/profile.j2 index f337ecd..7fef803 100644 --- a/templates/profile.j2 +++ b/templates/profile.j2 @@ -16,16 +16,18 @@
+ +

Your Pastes


Shortened Urls

- {{ table.build(links) }} + {{ table.build(links, 'links') }}

Pastes

- {{ table.build(pastes) }} + {{ table.build(pastes, 'pastes') }}

Images

- {{ table.build(images) }} + {{ table.build(images, 'images') }}
{% endblock %} @@ -45,11 +47,11 @@ function showSuccess(message) { $('#success-alert').show(); } -function hideError(error) { +function hideError() { $('#error-alert').hide(); } -function hideSuccess(error) { +function hideSuccess() { $('#success-alert').hide(); } @@ -57,5 +59,33 @@ function showApps () { $("#apps").toggle(); $("#overlay").toggle(); } + +const deleteEntry = (game, table) => { + fetch('/delete', { + method: 'POST', + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({'table': table, 'id': game}) + }) + .then(res => res.json()) // parse response as JSON (can be res.text() for plain response) + .then(response => { + // here you do what you want with response + console.log(response); + + if (response.success) { + showSuccess(response.msg); + hideError(); + document.querySelector('#' + table + "-" + game).remove(); + } else { + showError(response.msg); + hideSuccess(); + } + }) + .catch(err => { + console.log(err); + + showError('An error occured!'); + hideSuccess(); + }); +}; {% endblock %} \ No newline at end of file diff --git a/templates/text.html b/templates/text.html deleted file mode 100644 index b8c442a..0000000 --- a/templates/text.html +++ /dev/null @@ -1,6 +0,0 @@ - - -

CodeMirror!

-
- - \ No newline at end of file
{{ link[1] }}Delete
{{ link[1] }}Delete