aboutsummaryrefslogtreecommitdiff
path: root/pastebin.py
blob: 3851d0131f7f1f563cc8629c0739cece510ce4cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from datetime import datetime
from itsdangerous import Signer
from flask import (Flask, request, url_for, redirect, g, render_template, session, abort)
from flask_sqlalchemy import SQLAlchemy

## ripped from https://stackoverflow.com/questions/183042/how-can-i-use-uuids-in-sqlalchemy/812363#812363
from sqlalchemy import types
from sqlalchemy.dialects.mysql.base import MSBinary
from sqlalchemy.schema import Column
import uuid
class UUID(types.TypeDecorator):
   impl = MSBinary
   def __init__(self):
      self.impl.length = 16
      types.TypeDecorator.__init__(self,length=self.impl.length)
   def process_bind_param(self,value,dialect=None):
      if value and isinstance(value,uuid.UUID):
         return value.bytes
      elif value and not isinstance(value,uuid.UUID):
         raise ValueError('value %s is not a valid uuid.UUID' % value)
      else:
         return None
   def process_result_value(self,value,dialect=None):
      if value:
         return uuid.UUID(bytes=value)
      else:
         return None
   def is_mutable(self):
      return False
id_column_name = "id"
def id_column():
    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)

def url_for_other_page(page):
   args = request.view_args.copy()
   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']

def refresh_string(delay,url):
   """
   Returns a string for html content for redirecting the user back after the
   requested delay, to the requested url.
   """
   return f'<meta http-equiv="Refresh" content="{delay}; url={url}">'

@app.before_request
def check_user_status():
   g.user = None
   if 'user_id' in session:
      g.user = User.query.get(session['user_id'])

class Paste(db.Model):
   id = id_column()
   code = db.Column(db.Text)
   title = db.Column(db.Text)
   pub_date = db.Column(db.DateTime)
   user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
   is_private = db.Column(db.Boolean)
   parent_id = db.Column(UUID(), db.ForeignKey('paste.id'))
   parent = db.relationship('Paste', lazy=True, backref='children', uselist=False, remote_side=[id])

   def __init__(self, user, code, title, parent=None, is_private=False):
      self.user = user
      self.code = code
      self.title = title
      self.is_private = is_private
      self.pub_date = datetime.utcnow()
      self.parent = parent

class User(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   display_name = db.Column(db.String(120))
   fb_id = db.Column(db.String(30), unique=True)
   pastes = db.relationship(Paste, lazy='dynamic', backref='user')

@app.route('/', methods=['GET', 'POST'])
def new_paste():
   parent = None
   reply_to = request.args.get('reply_to')
   if reply_to is not None:
      try:
         parent = Paste.query.get(uuid.UUID(reply_to))
      except:
         parent = Paste.query.get(reply_to)
   if request.method == 'POST' and request.form['code']:
      is_private = bool(request.form.get('is_private'))
      title = "Untitled paste"
      if request.form['pastetitle'] and request.form['pastetitle'] != "Enter title here":
         title = request.form['pastetitle']
      paste = Paste(g.user, request.form['code'], title, parent=parent, is_private=is_private)
      db.session.add(paste)
      db.session.commit()
      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)

@app.route('/<paste_id>/')
@app.route('/<paste_id>')
def show_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))
   if paste.is_private:
      try:
         sign = request.args.get('s', '')
         assert str(paste.id) == \
            get_unsigned(sign, salt=app.config['SALT'])
      except:
         abort(403)
   parent = None
   if paste.parent_id:
      try:
         parent = Paste.query.get(uuid.UUID(paste.parent_id))
      except:
         parent = Paste.query.get(paste.parent_id)
   children = []
   if paste.children:
      for i in paste.children:
         j = None
         try:
            j = Paste.query.get(uuid.UUID(i.id))
         except:
            j = Paste.query.get(i.id)
         if j:
            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()
      message = refresh_string(1, url_for("admin")) + "OK"
      return message,200
   except:
      return f"failure to delete object. Select <a href='{url_for('admin')}'>here</a> to return to the admin panel.",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)
bgstack15