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 147aa74..45a3271 100644 Binary files a/links/links.db and b/links/links.db differ 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