aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README-bgstack15.md31
-rw-r--r--pastebin.py75
-rw-r--r--templates/admin.html21
-rw-r--r--templates/layout.html2
4 files changed, 122 insertions, 7 deletions
diff --git a/README-bgstack15.md b/README-bgstack15.md
index 10815e2..e0cd9bc 100644
--- a/README-bgstack15.md
+++ b/README-bgstack15.md
@@ -1,17 +1,42 @@
+<!--
+ -- Filename: README-bgstack15.md
+ -- Startdate: 2022-02-11
+ --
+ -->
+# Overview
+This is my proposed solution to my pastebin problem.
+
+# Features
+
+* Admin page which can list parents, children, and provide link to delete pastes.
+* Editable titles
+* "Reply to" pastes to make parent/children relationships
+* UUIDs instead of sequential integer ID numbers
+* Private pastes (accessible to admin, and to users with the whole link)
+
# Instructions
Generate new db.
python3 initdb.py
-Run server.
+Run server in development mode.
FLASK_APP=pastebin.py FLASK_DEBUG=True flask run --host='0.0.0.0'
# Improvements
-I still need to practice these:
+I still need to work on these tasks:
+
+## Development
+
+* Protect the /admin/ page
+* Add expiry of pastes? (use existing pubdate value, or just an extra column with desired expiration timestamp)
+
+## Release
-* Support deleting somehow: from an admin panel, or a link on the page?
+* Read any of my flask projects (fuss is the best one) to learn how to setup prod server
+* Document centos7 dependencies
+* Deploy to prod
# Alternatives
diff --git a/pastebin.py b/pastebin.py
index 37fda53..4c529f9 100644
--- a/pastebin.py
+++ b/pastebin.py
@@ -29,9 +29,14 @@ class UUID(types.TypeDecorator):
return False
id_column_name = "id"
def id_column():
- #import uuid
return Column(id_column_name,UUID(),primary_key=True,default=uuid.uuid4)
+def get_signed(string, salt="blank"):
+ return Signer(app.secret_key, salt=salt).sign(str(string))
+
+def get_unsigned(string, salt="blank"):
+ return Signer(app.secret_key, salt=salt).unsign(str(string)).decode("utf-8")
+
app = Flask(__name__)
app.config.from_pyfile('config.cfg')
db = SQLAlchemy(app)
@@ -41,6 +46,7 @@ def url_for_other_page(page):
args['page'] = page
return url_for(request.endpoint, **args)
app.jinja_env.globals['url_for_other_page'] = url_for_other_page
+app.jinja_env.globals['appname'] = app.config['APPNAME']
@app.before_request
def check_user_status():
@@ -89,7 +95,7 @@ def new_paste():
paste = Paste(g.user, request.form['code'], title, parent=parent, is_private=is_private)
db.session.add(paste)
db.session.commit()
- sign = Signer(app.secret_key, salt=app.config['SALT']).sign(str(paste.id)) \
+ sign = get_signed(paste.id, salt=app.config['SALT']) \
if is_private else None
return redirect(url_for('show_paste', paste_id=paste.id, s=sign))
return render_template('new_paste.html', parent=parent)
@@ -105,7 +111,7 @@ def show_paste(paste_id):
try:
sign = request.args.get('s', '')
assert str(paste.id) == \
- Signer(app.secret_key, salt=app.config['SALT']).unsign(sign).decode("utf-8")
+ get_unsigned(sign, salt=app.config['SALT'])
except:
abort(403)
parent = None
@@ -126,3 +132,66 @@ def show_paste(paste_id):
k = j.id, j.title
children.append(k)
return render_template('show_paste.html', paste=paste, parent=parent, children=children)
+
+@app.route('/<paste_id>/delete/', methods=['POST'])
+@app.route('/<paste_id>/delete', methods=['POST'])
+def delete_paste(paste_id):
+ try:
+ paste = Paste.query.options(db.eagerload('children')).get_or_404(paste_id)
+ except:
+ paste = Paste.query.options(db.eagerload('children')).get_or_404(uuid.UUID(paste_id))
+ sign = str(request.form['s'])
+ try:
+ assert str(paste.id) == get_unsigned(sign, salt=app.config['DELETESALT'])
+ except:
+ abort(403)
+ try:
+ Paste.query.filter(Paste.id == paste.id).delete()
+ db.session.commit()
+ return "OK",200 # WORKHERE: make this and the 500 redirect to admin/
+ except:
+ return "failure to delete object.",500
+
+def get_all_pastes():
+ """
+ Get custom arrangement of pastes for Admin view
+ """
+ all1 = Paste.query.all()
+ all2 = []
+ for p1 in all1:
+ parent_id = None
+ parent_title = None
+ children = []
+ if p1.parent_id:
+ parent_id = p1.parent_id
+ try:
+ parent_title = Paste.query.get(p1.parent_id).title
+ except:
+ parent_title = "" # works better than None for the parent column of the generated html
+ if p1.children:
+ for c1 in p1.children:
+ child = Paste.query.get(c1.id)
+ child_title = child.title
+ c2 = c1.id, child_title
+ children.append(c2)
+ private = None
+ if p1.is_private:
+ private = get_signed(p1.id, salt=app.config['SALT'])
+ p2 = {
+ "id": p1.id,
+ "title": p1.title,
+ "private": private,
+ "user_id": p1.user_id,
+ "is_private": p1.is_private,
+ "parent": (parent_id, parent_title),
+ "children": children,
+ "delete": get_signed(p1.id, salt=app.config['DELETESALT']).decode("utf-8")
+ }
+ all2.append(p2)
+ return all2
+
+@app.route('/admin/')
+@app.route('/admin')
+def admin():
+ all_pastes = get_all_pastes()
+ return render_template('admin.html', pastes = all_pastes)
diff --git a/templates/admin.html b/templates/admin.html
new file mode 100644
index 0000000..a815fb5
--- /dev/null
+++ b/templates/admin.html
@@ -0,0 +1,21 @@
+{% extends "layout.html" %}
+{% block title %}Administration{% endblock %}
+{% block body %}
+<h1>Administration for {{ appname }}</h1>
+{% if pastes %}
+<table>
+<tr><th>id</th><th>private</th><th>title</th><th>user</th><th>parent</th><th>children</th><th>Actions</th></tr>
+{% for p in pastes %}
+<tr>
+<td>{{ p.id }}</td>
+<td>{% if p.private %}&#10003;{% endif %}</td>{# magic string is from utf8icons.com #}
+<td><a href="{% if not p.private %}{{ url_for('show_paste', paste_id=p.id) }}{% else %}{{ url_for('show_paste', paste_id=p.id, s=p.private) }}{% endif %}">{{ p.title }}</a></td>
+<td>{% if p.user %}{{ p.user }}{% endif%}</td>
+<td>{% if p.parent[0] %}<a href="{{ url_for('show_paste', paste_id=p.parent[0]) }}">{{ p.parent[1] }}</a>{% endif %}</td>
+<td>{% if p.children %}{% for c in p.children %}{% if not loop.first %},{% endif %}
+<a href="{{ url_for('show_paste', paste_id=c[0]) }}">{{ c[1] }}</a>{% endfor %}{% endif %}
+</td>
+<td><form method="post" action="{{ url_for('delete_paste', paste_id=p.id) }}"><input type="submit" value="delete"/> <input type="hidden" id="s" name="s" value="{{ p.delete }}"/></form></a></td>
+{% endfor %}
+{% endif %}
+{% endblock %}
diff --git a/templates/layout.html b/templates/layout.html
index bfe8b05..fc2d8e5 100644
--- a/templates/layout.html
+++ b/templates/layout.html
@@ -1,5 +1,5 @@
<!doctype html>
-<title>{% block title %}{% endblock %} | Flask Pastebin</title>
+<title>{% block title %}{% endblock %}{% if appname %} | {{ appname }}{% endif %}</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
bgstack15