diasporadiaries

a platform for writing stories with personal accounts and messages
git clone https://git.parazyd.org/diasporadiaries
Log | Files | Refs | Submodules | README | LICENSE

diaspora.py (15306B)


      1 #!/usr/bin/env python3
      2 # Copyright (c) 2019 Ivan Jelincic <parazyd@dyne.org>
      3 #
      4 # This file is part of diasporadiaries
      5 #
      6 # This program is free software: you can redistribute it and/or modify
      7 # it under the terms of the GNU Affero General Public License as published by
      8 # the Free Software Foundation, either version 3 of the License, or
      9 # (at your option) any later version.
     10 #
     11 # This program is distributed in the hope that it will be useful,
     12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 # GNU Affero General Public License for more details.
     15 #
     16 # You should have received a copy of the GNU Affero General Public License
     17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18 """
     19 Main diasporadiaries module
     20 """
     21 from random import choice
     22 from time import time
     23 
     24 from bcrypt import hashpw, gensalt
     25 from flask import Markup, Flask, render_template, request, redirect
     26 from flask_login import (LoginManager, UserMixin, login_required, login_user,
     27                          logout_user, current_user)
     28 
     29 from config import SECRET_KEY
     30 from db import (sql_delete_row_where, sql_update_row_where, sql_insert,
     31                 sql_select_col, sql_select_col_where)
     32 from utils import (get_story, makenav, randomstring, getcountryname,
     33                    get_multiple_stories, get_multiple_stories_filtered,
     34                    make_profile, find_user_by_id, find_user_by_email,
     35                    validate_user, get_multiple_users, get_messages,
     36                    get_latest_messages, send_message, delete_user,
     37                    get_recent_stories, follow_user, unfollow_user,
     38                    is_following, get_following, send_shout, get_shouts,
     39                    get_profiles_from_stories, parsetime, delete_shout)
     40 
     41 
     42 app = Flask(__name__)
     43 app.config['SECRET_KEY'] = SECRET_KEY
     44 app.add_template_global(makenav)
     45 app.add_template_global(parsetime)
     46 
     47 login_manager = LoginManager()
     48 login_manager.init_app(app)
     49 login_manager.login_view = 'login'
     50 
     51 
     52 class LoginUser(UserMixin):
     53     """
     54     Class for a user
     55     """
     56     # TODO: Find out if it's possible to do less SQL queries.
     57     def __init__(self, uid):
     58         """ Init """
     59         self.id = uid
     60 
     61     @property
     62     def username(self):
     63         """ Username """
     64         user = self.get_user()
     65         return user['email']
     66 
     67     @property
     68     def name(self):
     69         """ Name """
     70         user = self.get_user()
     71         return user['name']
     72 
     73     @property
     74     def is_admin(self):
     75         """ Check capabilities """
     76         user = self.get_user()
     77         if user['cap'] == 2:
     78             return True
     79         return False
     80 
     81     def get_user(self):
     82         """ Get user struct """
     83         return find_user_by_id(self.id)
     84 
     85 
     86 @login_manager.user_loader
     87 def load_user(user_id):
     88     """
     89     flask-security function for loading the LoginUser class for a user.
     90     """
     91     if not find_user_by_id(user_id):
     92         return None
     93     return LoginUser(uid=user_id)
     94 
     95 
     96 @app.route('/login', methods=['GET', 'POST'])
     97 def login():
     98     """
     99     User login route.
    100     """
    101     if request.method == 'POST':
    102         if request.form.get('42'):
    103             return render_template('fail.html', msg='You robot!')
    104 
    105         username = request.form['username']
    106         password = request.form['password']
    107 
    108         user = find_user_by_email(username)
    109         if not user:
    110             return render_template('fail.html', msg='No such user.')
    111 
    112         if validate_user(user, password):
    113             login_user(LoginUser(user['id']), remember=True)
    114 
    115             if user['is_active'] == 0:
    116                 return redirect('/changepass')
    117 
    118             nxt = request.args.get('next')
    119             if not nxt:
    120                 return redirect('/')
    121         else:
    122             return render_template('fail.html', msg='Incorrect password.')
    123 
    124     return render_template('login.html')
    125 
    126 
    127 @app.route('/changepass', methods=['GET', 'POST'])
    128 @login_required
    129 def changepass():
    130     """
    131     Route for changing passwords.
    132     """
    133     user = find_user_by_email(current_user.username)
    134 
    135     if request.method != 'POST':
    136         return render_template('change_pass.html', first=user['is_active'])
    137 
    138     if request.form['42']:
    139         return render_template('fail.html', msg='You robot!')
    140 
    141     old_in_db = user['password']
    142     old_in_pg = request.form.get('oldpassword')
    143     new_in_pg = request.form.get('newpassword')
    144     if not old_in_pg or not new_in_pg:
    145         return render_template('fail.html',
    146                                msg='Please input both old and new passwords!')
    147 
    148     old_hashed = hashpw(old_in_pg.encode(), old_in_db)
    149     if old_hashed == old_in_db:
    150         hashed = hashpw(new_in_pg.encode(), gensalt())
    151         sql_update_row_where([('password', hashed), ('is_active', 1)],
    152                              'email', user['email'], table='users')
    153     else:
    154         return render_template('fail.html', msg='Old password is incorrect.')
    155 
    156     return render_template('success.html', msg='Password change success!')
    157 
    158 
    159 @app.route('/logout')
    160 @login_required
    161 def logout():
    162     """
    163     User logout route.
    164     """
    165     logout_user()
    166     return render_template('success.html', msg='Successfully logged out!')
    167 
    168 
    169 @app.route('/messages')
    170 @login_required
    171 def messages():
    172     """
    173     Route for users' messages.
    174     """
    175     id_from = request.args.get('from')
    176     if id_from:
    177         msgs = get_messages(current_user.username, id_from)
    178         return render_template('messages.html', messages=msgs[::-1],
    179                                single=True, them=find_user_by_id(id_from))
    180 
    181     msgs = get_latest_messages(current_user.username)
    182     return render_template('messages.html', messages=msgs)
    183 
    184 
    185 @app.route('/follow', methods=['POST'])
    186 @login_required
    187 def follow():
    188     """
    189     Route for following a user.
    190     """
    191 
    192     if request.form.get('Act') == 'follow':
    193         follow_user(current_user.id, request.form.get('Id'))
    194     elif request.form.get('Act') == 'unfollow':
    195         unfollow_user(current_user.id, request.form.get('Id'))
    196 
    197     if request.form.get('From') == '/following':
    198         return redirect('/following')
    199 
    200     return redirect('/profile?id=%s' % request.form.get('Id'))
    201 
    202 
    203 @app.route('/sendmsg', methods=['POST'])
    204 @login_required
    205 def sendmsg():
    206     """
    207     Route for sending a single message.
    208     """
    209     send_message(request.form.get('Id'), request.form.get('Message'),
    210                  request.form.get('Us'))
    211     return redirect('/messages?from=%s' % request.form.get('Id'))
    212 
    213 
    214 @app.route('/sendshout', methods=['POST'])
    215 @login_required
    216 def sendshout():
    217     """
    218     Route for leaving a shout.
    219     """
    220     send_shout(request.form.get('Id'), request.form.get('Message'),
    221                request.form.get('Us'))
    222     return redirect('/profile?id=%s' % request.form.get('Id'))
    223 
    224 
    225 @app.route('/delshout', methods=['POST'])
    226 @login_required
    227 def delshout():
    228     """
    229     Route for deleting a shout.
    230     """
    231     delete_shout(request.form.get('Id'), request.form.get('ShoutId'))
    232     return redirect('/profile?id=%s' % request.form.get('Id'))
    233 
    234 
    235 @app.route('/write', methods=['GET', 'POST'])
    236 def write():
    237     """
    238     Route for writing a story.
    239     """
    240     if request.method != 'POST':
    241         return render_template('write.html')
    242 
    243     if request.form.get('42'):
    244         return render_template('fail.html', msg='Are you a robot?')
    245 
    246     delkey = randomstring(32)
    247     storyargs = [
    248         None,
    249         request.form.get('Name', 'Anonymous writer'),
    250         request.form.get('Embark', 'nn'),
    251         request.form.get('Disembark', 'nn'),
    252         request.form.get('Email'),
    253         request.form.get('City'),
    254         request.form.get('About'),
    255         request.form.get('Story', 'foobar'),
    256         int(time()),
    257         0,
    258         delkey,
    259         Markup(request.form['Story']).striptags()[:127],
    260     ]
    261     sql_insert(storyargs)
    262 
    263     new_account = False
    264     if request.form.get('Email'):
    265         new_account = make_profile(request.form.get('Name', 'Anonymous user'),
    266                                    request.form.get('Email'))
    267 
    268     return render_template('success_write.html', delkey=delkey,
    269                            new=new_account)
    270 
    271 
    272 @app.route('/edit', methods=['GET', 'POST'])
    273 @login_required
    274 def edit():
    275     """
    276     Route for editing and redacting.
    277     """
    278     if request.method != 'POST':
    279         story = get_story(request.args.get('id'))
    280         if story:
    281             if current_user.is_admin or current_user.username == story['email']:
    282                 return render_template('edit.html', story=story)
    283             return render_template('fail.html', msg='Unauthorized.')
    284 
    285         return render_template('fail.html', msg='No story with this story id.')
    286 
    287     vals = [
    288         ('name', request.form.get('Name', 'Anonymous user')),
    289         ('embark', request.form.get('Embark', 'nn')),
    290         ('disembark', request.form.get('Disembark', 'nn')),
    291         ('email', request.form.get('Email')),
    292         ('city', request.form.get('City')),
    293         ('story', request.form.get('Story', 'foo')),
    294         ('about', request.form.get('About')),
    295         ('abstract', request.form.get('Abstract', 'foo')),
    296     ]
    297 
    298     if request.form.get('Id'):
    299         story = get_story(request.form.get('Id'))
    300         if current_user.is_admin or current_user.username == story['email']:
    301             sql_update_row_where(vals, 'id', request.form.get('Id'))
    302         return render_template('success.html', msg='Story redacted!')
    303 
    304     return render_template('fail.html', msg='Redaction failed.')
    305 
    306 
    307 @app.route('/view')
    308 def view():
    309     """
    310     Route for viewing a single story.
    311     If no story is specified, it renders a random story.
    312     """
    313     story_id = request.args.get('id')
    314     if not story_id:
    315         story_id = choice(sql_select_col('id'))[0]
    316 
    317     story = get_story(story_id)
    318     user = find_user_by_email(story['email'])
    319 
    320     return render_template('view.html', story=story, user=user)
    321 
    322 
    323 @app.route('/delete')
    324 def delete():
    325     """
    326     Route for deleting a specific story.
    327     """
    328     delkey = request.args.get('key')
    329     if delkey:
    330         story_id = sql_select_col_where('id', 'deletekey', delkey)
    331         if story_id:
    332             story_id = story_id[0][0]
    333             sql_delete_row_where('id', story_id)
    334             return render_template('success.html',
    335                                    msg="We're sorry you deleted your story.")
    336 
    337     return render_template('fail.html',
    338                            msg='No story is linked to your delete key.')
    339 
    340 
    341 @app.route('/country')
    342 def country():
    343     """
    344     Route for viewing stories for a specific country.
    345     If no country is given, it will show a random country.
    346     """
    347     ccode = request.args.get('id')
    348     if not ccode:
    349         ccode = choice(sql_select_col('disembark'))[0]
    350 
    351     ccfrom = request.args.get('from')
    352     filtered = None
    353     if ccfrom:
    354         filtered = get_multiple_stories_filtered('disembark', ccode,
    355                                                  ('embark', ccfrom))
    356 
    357     stories = get_multiple_stories('disembark', ccode)
    358     profiles = get_profiles_from_stories(stories)
    359 
    360     clist = [(i['embark'], i['embarkname']) for i in stories]
    361     clist = list(set(clist))
    362 
    363     return render_template('country.html', country=getcountryname(ccode),
    364                            cc=ccode, stories=stories, clist=clist,
    365                            filtered_stories=filtered, profiles=profiles)
    366 
    367 
    368 @app.route('/profile')
    369 def profile():
    370     """
    371     Route for viewing stories by a specific person. The persons
    372     are differed by their emails.
    373     """
    374     uid = request.args.get('id')
    375     if uid:
    376         user = find_user_by_id(uid)
    377         if user:
    378             stories = get_multiple_stories_filtered('email', user['email'],
    379                                                     ('visible', 1))
    380 
    381             fol = False
    382             if current_user.is_active:
    383                 fol = is_following(current_user.username, user['email'])
    384             return render_template('profile.html', name=user['name'], uid=uid,
    385                                    stories=stories, profiles={},
    386                                    shouts=get_shouts(user['email']),
    387                                    is_following=fol)
    388 
    389     return render_template('fail.html', msg='No such profile.')
    390 
    391 
    392 @app.route('/following')
    393 @login_required
    394 def following():
    395     """
    396     Route for listing the users a user is following.
    397     """
    398     fol = []
    399     for i in get_following(current_user.username):
    400         j = find_user_by_email(i)
    401         if j:
    402             cnt = []
    403             countries = sql_select_col_where('disembark', 'email', j['email'])
    404             for k in countries:
    405                 cnt.append((k[0], getcountryname(k[0])))
    406             fol.append([j['id'], j['name'], cnt])
    407 
    408     return render_template('following.html', following=fol)
    409 
    410 
    411 @app.route('/dashboard')
    412 @login_required
    413 def dashboard():
    414     """
    415     Route for dashboard/admin view.
    416     """
    417     if not current_user.is_admin:
    418         return render_template('fail.html', msg="Unauthorized.")
    419 
    420     fparam = {'pending': 0, 'published': 1}
    421 
    422     add_id = request.args.get('approveid')
    423     if add_id:
    424         sql_update_row_where([('visible', 1)], 'id', add_id)
    425         return render_template('success.html', msg='Story approved!')
    426 
    427     hide_id = request.args.get('hideid')
    428     if hide_id:
    429         sql_update_row_where([('visible', 0)], 'id', hide_id)
    430         return render_template('success.html', msg='Story hidden.')
    431 
    432     act = request.args.get('list', 'pending')
    433 
    434     ccto = request.args.get('to')
    435     filtered = None
    436     if ccto:
    437         filtered = get_multiple_stories_filtered('visible', fparam[act],
    438                                                  ('disembark', ccto))
    439 
    440     stories = get_multiple_stories('visible', fparam[act])
    441     profiles = get_profiles_from_stories(stories)
    442 
    443     clist = [(i['disembark'], i['disembarkname']) for i in stories]
    444     clist = list(set(clist))
    445 
    446     return render_template('dashboard.html', stories=stories,
    447                            filtered_stories=filtered, profiles=profiles,
    448                            listtype=act, clist=clist, ccto=ccto)
    449 
    450 
    451 @app.route('/users', methods=['GET', 'POST'])
    452 @login_required
    453 def users():
    454     """
    455     Route for users view.
    456     """
    457     if not current_user.is_admin:
    458         return render_template('fail.html', msg='Unauthorized.')
    459 
    460     if request.method == 'POST':
    461         vals = []
    462         if request.form.get('password'):
    463             vals.append(('password',
    464                          hashpw(request.form.get('password').encode(),
    465                                 gensalt())))
    466         if request.form.get('cap'):
    467             vals.append(('cap', request.form.get('cap')))
    468 
    469         if vals and request.form.get('id'):
    470             sql_update_row_where(vals, 'id', request.form.get('id'),
    471                                  table='users')
    472 
    473     if request.args.get('delid'):
    474         delete_user(request.args.get('delid'))
    475 
    476     # userlist = get_multiple_users('is_active', 1)
    477     userlist = get_multiple_users(None, None)
    478     return render_template('users.html', users=userlist)
    479 
    480 
    481 @app.route('/contact')
    482 def contact():
    483     """
    484     Contact route
    485     """
    486     return render_template('contact.html')
    487 
    488 
    489 @app.route('/')
    490 def main():
    491     """
    492     Main route, the homepage.
    493     """
    494     stories = get_recent_stories()
    495     profiles = get_profiles_from_stories(stories)
    496     return render_template('index.html', stories=stories, profiles=profiles)