Browse Source

Add images and make profile page functional. Add 404 pages for links that don't resolve.

pastebin
Daniel Muckerman 3 years ago
parent
commit
93d419d8e7
12 changed files with 245 additions and 33 deletions
  1. +2
    -1
      .gitignore
  2. +111
    -13
      app.py
  3. BIN
      links/links.db
  4. +11
    -0
      static/style.css
  5. +29
    -0
      templates/404.j2
  6. +6
    -1
      templates/fragments/navbar.j2
  7. +5
    -5
      templates/fragments/table.j2
  8. +44
    -0
      templates/image.j2
  9. +1
    -1
      templates/link.j2
  10. +1
    -1
      templates/paste.j2
  11. +35
    -5
      templates/profile.j2
  12. +0
    -6
      templates/text.html

+ 2
- 1
.gitignore View File

@ -19,4 +19,5 @@ venv.bak/
users.db users.db
node_modules/
node_modules/
links/images/*

+ 111
- 13
app.py View File

@ -1,3 +1,4 @@
from flask.helpers import send_from_directory
import ldap as l import ldap as l
from ldap3 import Server, Connection from ldap3 import Server, Connection
from ldap3.core.exceptions import LDAPBindError from ldap3.core.exceptions import LDAPBindError
@ -9,6 +10,7 @@ from flask_wtf import FlaskForm
from flask_cache_buster import CacheBuster from flask_cache_buster import CacheBuster
from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
from werkzeug.utils import secure_filename
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
import short_url import short_url
import os 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_USERNAME'] = os.environ.get('LDAP_USERNAME')
app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD') app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD')
# Uploads
app.config['UPLOAD_FOLDER'] = 'links/images'
# OpenLDAP # OpenLDAP
app.config['LDAP_OBJECTS_DN'] = 'dn' app.config['LDAP_OBJECTS_DN'] = 'dn'
app.config['LDAP_OPENLDAP'] = True 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') #app.config['REMEMBER_COOKIE_DOMAIN'] = os.environ.get('COOKIE_DOMAIN')
short_domain = os.environ.get('SHORT_DOMAIN') short_domain = os.environ.get('SHORT_DOMAIN')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
db = SQLAlchemy(app) db = SQLAlchemy(app)
@ -132,7 +138,8 @@ def get_current_user():
def index(): def index():
pastes = get_pastes_for_user() pastes = get_pastes_for_user()
links = get_links_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') @app.route('/link')
@ -147,6 +154,37 @@ def text():
return render_template('paste.j2', user = current_user.get_user_dict()) 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 <a target='_blank' href='{}/i/{}'>{}/i/{}</a>".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']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if current_user.is_authenticated: if current_user.is_authenticated:
@ -207,7 +245,7 @@ def shorten_url():
conn.commit() conn.commit()
conn.close() conn.close()
url_fragment = short_url.encode_url(row[0]) url_fragment = short_url.encode_url(row[0])
return "Your shortened link is <a href='{}/l/{}'>{}/l/{}</a>".format(short_domain, url_fragment, short_domain, url_fragment)
return "Your shortened link is <a target='_blank' href='{}/l/{}'>{}/l/{}</a>".format(short_domain, url_fragment, short_domain, url_fragment)
conn.commit() conn.commit()
conn.close() conn.close()
return 'Error' return 'Error'
@ -230,21 +268,46 @@ def save_paste():
conn.commit() conn.commit()
conn.close() conn.close()
url_fragment = short_url.encode_url(row[0]) url_fragment = short_url.encode_url(row[0])
return {"success": True, "msg": "Your paste link is <a href='{}/p/{}'>{}/p/{}</a>".format(short_domain, url_fragment, short_domain, url_fragment)}
return {"success": True, "msg": "Your paste link is <a target='_blank' href='{}/p/{}'>{}/p/{}</a>".format(short_domain, url_fragment, short_domain, url_fragment)}
conn.commit() conn.commit()
conn.close() conn.close()
return {'success': False} 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/<url>') @app.route('/l/<url>')
def expand_url(url): def expand_url(url):
idx = short_url.decode_url(url) idx = short_url.decode_url(url)
conn = sqlite3.connect('links/links.db') conn = sqlite3.connect('links/links.db')
c = conn.cursor() c = conn.cursor()
c.execute("SELECT * FROM links WHERE id=?", (idx,)) 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/<url>') @app.route('/p/<url>')
@ -253,10 +316,26 @@ def show_paste(url):
conn = sqlite3.connect('links/links.db') conn = sqlite3.connect('links/links.db')
c = conn.cursor() c = conn.cursor()
c.execute("SELECT * FROM pastes WHERE id=?", (idx,)) 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/<url>')
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(): def get_pastes_for_user():
conn = sqlite3.connect('links/links.db') conn = sqlite3.connect('links/links.db')
@ -265,8 +344,9 @@ def get_pastes_for_user():
out = [] out = []
for row in c.fetchall(): for row in c.fetchall():
a = "{}/p/{}".format(short_domain, short_url.encode_url(row[0]))
out.append((row[0], a))
a = "{}/p/{}<span class='faded'> - {}</span>".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 return out
@ -278,13 +358,31 @@ def get_links_for_user():
out = [] out = []
for row in c.fetchall(): 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/{}<span class='faded'> - {}</span>".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 return out
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/logout') @app.route('/logout')
@login_required @login_required
def logout(): def logout():

BIN
links/links.db View File


+ 11
- 0
static/style.css View File

@ -139,4 +139,15 @@
top: 0; top: 0;
left: 0; left: 0;
overflow: hidden; overflow: hidden;
}
.faded {
color: white;
opacity: 0.25;
}
.clip-row {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }

+ 29
- 0
templates/404.j2 View File

@ -0,0 +1,29 @@
{% extends "bootstrap/base.html" %}
{% block title %}tia.paste{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">tia.paste</div>
</nav>
{% endblock %}
{% block content %}
<div class="container" style="margin-top: 15px; padding-bottom: 15px;">
<div class="row justify-content-center">
<div class="col-lg-12">
<h1 style="font-size: 96px; font-weight: bold; text-align: center;">404</h1>
<p style="text-align: center;">This page doesn't exist! How did you get here?</p>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{url_for('.static', filename='editor.bundle.js')}}"></script>
{% endblock %}

+ 6
- 1
templates/fragments/navbar.j2 View File

@ -18,8 +18,13 @@
<a class="nav-link" href="{{ url_for('text') }}">Paste</a> <a class="nav-link" href="{{ url_for('text') }}">Paste</a>
{% endif %} {% endif %}
</li> </li>
{% if request.path == url_for('image') %}
<li class="nav-item active">
<a class="nav-link" href="#">Image <span class="sr-only">(current)</span></a>
{% else %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#">Image</a>
<a class="nav-link" href="{{ url_for('image') }}">Image</a>
{% endif %}
</li> </li>
</ul> </ul>
{% endif %} {% endif %}

+ 5
- 5
templates/fragments/table.j2 View File

@ -1,11 +1,11 @@
{% macro build(links_list) %}
<table class="table table-striped table-dark" id="game-table">
{% macro build(links_list, table) %}
<table class="table table-striped table-dark" style="table-layout: fixed">
<tbody> <tbody>
{% if links_list is defined %} {% if links_list is defined %}
{% for link in links_list %} {% for link in links_list %}
<tr>
<th><a href="{{ link[1] }}" target="_blank" class="game-link">{{ link[1] }}</a></th>
<td style="vertical-align: middle; width: 50px;"><a href="#game-table" onclick="deleteGame({{ link[0] }})" class="text-danger">Delete</a></td>
<tr id="{{ table }}-{{ link[0] }}">
<th class="clip-row"><a href="{{ link[2] }}" target="_blank" class="game-link">{{ link[1] }}</a></th>
<td style="vertical-align: middle; width: 55px; padding-left: 0px;"><a href="#" onclick="deleteEntry({{ link[0] }}, '{{ table }}')" class="text-danger">Delete</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endif %} {% endif %}

+ 44
- 0
templates/image.j2 View File

@ -0,0 +1,44 @@
{% extends "bootstrap/base.html" %}
{% block title %}tia.paste{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
{% include "fragments/navbar.j2" %}
{% endblock %}
{% block content %}
<iframe id="apps" src="https://technicalincompetence.club/frame" width="305" height="400" class="shadow-lg overlay-frame" style="display: none;"></iframe>
<div id="overlay" style="display: none;" onclick="showApps();"></div>
<div class="container" style="margin-top: 15px">
{% if success_msg %}
<div id="success-alert" class="alert alert-success" role="alert">{{ success_msg }}</div>
{% endif %}
<div id="error-alert" class="alert alert-danger" role="alert" style="display: none;">{{ error_msg }}</div>
<form method="post" enctype="multipart/form-data">
<div class="row justify-content-center">
<div class="col-lg-12">
<label>Select an image:</label>
<br>
<input type=file name=file>
<br><br>
<button type="submit" class="btn btn-primary" id="submit">Upload Image</button>
<br>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
function showApps () {
$("#apps").toggle();
$("#overlay").toggle();
}
</script>
{% endblock %}

+ 1
- 1
templates/link.j2 View File

@ -23,7 +23,7 @@
<form> <form>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-12"> <div class="col-lg-12">
<label for="formGroupExampleInput4">URL</label>
<label for="formGroupExampleInput4">Enter a link to shorten:</label>
<input id="link-form" type="text" class="form-control" placeholder="https://example.com"> <input id="link-form" type="text" class="form-control" placeholder="https://example.com">
<br> <br>
<button type="button" class="btn btn-primary" onclick="shortenUrl();">Shorten Link</button> <button type="button" class="btn btn-primary" onclick="shortenUrl();">Shorten Link</button>

+ 1
- 1
templates/paste.j2 View File

@ -19,7 +19,7 @@
<form> <form>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-12"> <div class="col-lg-12">
<label>Paste</label>
<label>Enter some text:</label>
<div id="editor"></div> <div id="editor"></div>
<br> <br>
<button type="button" class="btn btn-primary" id="submit">Save Paste</button> <button type="button" class="btn btn-primary" id="submit">Save Paste</button>

+ 35
- 5
templates/profile.j2 View File

@ -16,16 +16,18 @@
<div id="overlay" style="display: none;" onclick="showApps();"></div> <div id="overlay" style="display: none;" onclick="showApps();"></div>
<div class="container" style="margin-top: 15px"> <div class="container" style="margin-top: 15px">
<div class="col-lg-12"> <div class="col-lg-12">
<div id="success-alert" class="alert alert-success" role="alert" style="display: none;"></div>
<div id="error-alert" class="alert alert-danger" role="alert" style="display: none;"></div>
<h1>Your Pastes</h1> <h1>Your Pastes</h1>
<br> <br>
<h3>Shortened Urls</h3> <h3>Shortened Urls</h3>
{{ table.build(links) }}
{{ table.build(links, 'links') }}
<br> <br>
<h3>Pastes</h3> <h3>Pastes</h3>
{{ table.build(pastes) }}
{{ table.build(pastes, 'pastes') }}
<br> <br>
<h3>Images</h3> <h3>Images</h3>
{{ table.build(images) }}
{{ table.build(images, 'images') }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@ -45,11 +47,11 @@ function showSuccess(message) {
$('#success-alert').show(); $('#success-alert').show();
} }
function hideError(error) {
function hideError() {
$('#error-alert').hide(); $('#error-alert').hide();
} }
function hideSuccess(error) {
function hideSuccess() {
$('#success-alert').hide(); $('#success-alert').hide();
} }
@ -57,5 +59,33 @@ function showApps () {
$("#apps").toggle(); $("#apps").toggle();
$("#overlay").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();
});
};
</script> </script>
{% endblock %} {% endblock %}

+ 0
- 6
templates/text.html View File

@ -1,6 +0,0 @@
<!doctype html>
<meta charset=utf8>
<h1>CodeMirror!</h1>
<div id="editor"></div>
<button id="submit">Submit</button>
<script src="../editor.bundle.js"></script>

Loading…
Cancel
Save