"""
Views
------
This module contains all the business logic related to the app 'views'/URLs and how the server interacts
with the database.
Because it's a simple enough app, for now, queries are directly written instead of being processed by something like SQLAlchemy.
All AJAX POST functions return a new CSRF-protection token as the token is per request, not per session.
Division
~~~~~~~~
1. Function definitions.
2. Basic/rendered views.
3. API for AJAX requests.
Database
~~~~~~~~
In order for the following to make sense, here's the database schema/logic:
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE | COLUMNS |
+===============+==========================================================================================================================================================+
| USERS | - ID |
| | - Name (optional) |
| | - Username |
| | - UserEmail |
| | - Join_Date (defaults to date of creation of row) |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| | - ID |
| | - Title |
| | - CreatorID |
| | - Created_at (defaults to date of creation) |
| | - Done_at (Created_at + 24hrs at creation) |
| BOARDS | - Locked_until (Locks are placed when someone edits the board and last 5 seconds to prevent editing the same thing at the same time by someone else. |
| | This column defaults to date of creation and is changed later) |
| | - Locked_by (Who, in terms of userID or email, was the last to lock the board) |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| | - ID |
| | - BoardID |
| | - UserEmail (optional, in case an invited but not signed in user creates a component instance) |
| | - UserID (optional, in case user is signed in) |
| BOARD_CONTENT | - Created_at |
| | - Last_modified_at |
| | - Last_modified_by |
| | - Position (optional, for components whose positions matter, like text) |
| | - Deleted (boolean, 'N' or 'Y') |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| INVITES | - ID (UUID instead of auto-incrementing integer) |
| | - BoardID |
| | - UserEmail |
| | - Invite_date |
| | - Type (view or edit) |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+
"""
from pegasus import app
import sqlite3
import uuid
import string
import random
from flask import request, session, g, redirect, url_for, abort, render_template, flash, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime, timedelta
from itertools import islice
# all the definitions
[docs]def get_random_string(length=32):
"""Generate a random string of length 32, used in ``generate_csrf_token()``"""
return ''.join(random.choice(string.ascii_letters + string.digits) for i in range(length))
[docs]def generate_csrf_token():
"""Create a CSRF-protection token if one doesn't already exist in the user's session and put it there."""
if '_csrf_token' not in session:
session['_csrf_token'] = get_random_string()
return session['_csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
"""Whenever `{{ csrf_token() }}` is used in a Jinja2 template, it returns the result of the function `generate_csrf_token()` """
[docs]def login_user(username):
"""Login user using their username. Put username and userid (find in database) in their respective sessions."""
session['logged_in'] = True
session['username'] = username
cur = g.db.execute('select id from users where username=?', [username]).fetchone()
uid = cur[0]
session['userid'] = uid # to register any info in another table where userid is a FK instead of querying every time
[docs]def is_owner(boardID, userID):
"""Check if a user is the owner of a certain board."""
cur = g.db.execute('select creatorID from boards where id=?', [boardID]).fetchone()[0]
if cur == userID:
return True
else:
return False
[docs]def lock_board(boardID, userID=None, userEmail=None):
"""Called after making sure the board isn't currently locked and the user has editing access.
Any sqlite3.Error s handled in calling function.
Lock the board for 5 seconds.
"""
user = userID if userID is not None else userEmail
lock = datetime.utcnow() + timedelta(seconds=5)
lock_time = lock.strftime('%Y-%m-%d %H:%M:%S')
g.db.execute('update boards set locked_until=?, locked_by=? where id=?', [lock_time, user, boardID])
g.db.commit()
[docs]def is_authorized(boardID, wantToEdit=False):
"""Check if a certain signed in user (who by default doesn't want to edit the board) is authorized to access it,
and if yes, what's the extent of their access? View/Edit/Owner.
If edit/owner, can they edit now? (if the board is not locked, this function will lock it for them).
Returns a hash that includes access (boolean), isOwner (boolean), canEditNow (boolean), and accessType (str)
"""
access = False
isOwner = False
accessType = None
canEditNow = False
if session.get('logged_in'):
# not counting in the invitation link logic here
uid = session['userid']
# are they the owner?
if is_owner(boardID, uid):
access = True
isOwner = True
accessType = 'edit'
else:
uemail = g.db.execute('select email from users where id=?', [uid]).fetchone()[0]
cur2 = g.db.execute('select type from invites where boardID=? and userEmail=?', [boardID, uemail]).fetchone()
if cur2 is not None:
access = True
accessType = cur2[0]
if accessType =='edit' and wantToEdit:
# boardID must exist at this point, checked by calling functions
lock = g.db.execute('select locked_until, locked_by from boards where id=?', [boardID]).fetchone()
if lock is not None:
lockedUntil = datetime.strptime(lock[0], '%Y-%m-%d %H:%M:%S')
lockedBy = lock[1]
if (datetime.utcnow() > lockedUntil) or (datetime.utcnow() < lockedUntil and int(lockedBy) == uid):
try:
lock_board(boardID, userID=str(uid))
canEditNow = True
except sqlite3.Error as e:
pass
return {'access':access, 'isOwner':isOwner, 'accessType':accessType, 'canEditNow':canEditNow}
# routing (views)
@app.route('/')
[docs]def index():
"""Helpers for the index page."""
if session.get('logged_in'):
email = g.db.execute('select email from users where id=?', [session['userid']]).fetchone()[0].lower()
cur2 = g.db.execute('select id, title from boards where id in (select boardID from invites where userEmail=?)', [email]).fetchall()
invitedLi = [dict(id=row[0], title=row[1]) for row in cur2]
return render_template('show_list.html', invitedBoards=invitedLi)
else:
cur = g.db.execute('select username, join_date from users order by id')
li = [dict(username=row[0], jdate=row[1]) for row in cur.fetchall()]
return render_template('show_list.html', li=li)
@app.route('/register', methods=['GET', 'POST'])
[docs]def register_user():
"""If not logged in and POST, register new user and go to index. If an error occurs, render the same register template again but with an error."""
if session.get('logged_in'):
abort(401)
error = None
if request.method == 'POST':
try:
pw = generate_password_hash(request.form['password'], method='pbkdf2:sha512:10000')
un = request.form['username'].lower()
em = request.form['email'].lower()
g.db.execute('insert into users (username, password, email, name) values (?, ?, ?, ?)', [un, pw, em, request.form['name']])
g.db.commit()
login_user(un)
flash('Successfully registered!')
return redirect(url_for('index'))
except sqlite3.IntegrityError as e:
if e.args[0][32:] == 'email':
error = 'Email is already in use.'
elif e.args[0][32:] == 'username':
error = 'Username is already in use.'
else:
error = e.args[0]
return render_template('register.html', error=error)
@app.route('/login', methods=['GET', 'POST'])
[docs]def login():
"""Attempt login. If credentials check out, go to index. If not, render login template with error."""
if session.get('logged_in'):
abort(401)
error = None
if request.method == 'POST':
cur = g.db.execute('select username, password from users where username=?', [request.form['username'].lower()])
cur_res = cur.fetchone()
if cur_res is None:
error = 'Invalid username'
else:
username = cur_res[0]
pw = cur_res[1]
if check_password_hash(pw, request.form['password']) == False: # ouch
error = 'Invalid password'
else:
login_user(username)
flash('Hey there!', 'info')
return redirect(url_for('index'))
return render_template('login.html', error=error)
@app.route('/profile')
[docs]def show_profile():
"""Render profile for logged in user."""
if not session.get('logged_in'):
abort(401)
uid = session.get('userid')
cur = g.db.execute('select name, email from users where id=?', [uid]).fetchone()
cur2 = g.db.execute('select id, title from boards where creatorID=?', [uid]).fetchall()
boards = [dict(id=row[0], title=row[1]) for row in cur2]
return render_template('profile.html', name=cur[0], email=cur[1], boards=boards)
@app.route('/logout')
[docs]def logout():
"""Logout currently logged in user and redirect to home."""
session.pop('logged_in', None)
session.pop('username', None)
session.pop('userid', None)
flash('You go bye bye :(', 'warning')
return redirect(url_for('index')) # always going there..
@app.route('/new-board', methods=['GET', 'POST'])
[docs]def create_board():
"""Create a new board."""
if not session.get('logged_in'):
abort(401)
error = None
if request.method == 'POST':
try:
uid = session.get('userid')
title = request.form['title']
done = datetime.utcnow() + timedelta(days=1)
done_at = done.strftime('%Y-%m-%d %H:%M:%S')
locked_by = str(uid)
cur = g.db.cursor()
cur.execute('insert into boards (creatorID, title, done_at, locked_by) values (?, ?, ?, ?)', [uid, title, done_at, uid])
g.db.commit()
boardID = cur.lastrowid
cur.close()
flash('Board successfully created!')
return redirect(url_for('show_board', boardID=boardID))
except sqlite3.Error as e:
error = 'An error occured: ' + e.args[0]
return render_template('new-board.html', error=error)
@app.route('/board/<boardID>')
[docs]def show_board(boardID):
"""Show board with a specified `boardID`.
Hierarchy of errors:
1. *404* : board not found.
2. *401* : not authorized. Person trying to view the board is not logged in with access to this board or does not have a (valid) invite code.
Renders the page (initially) according to the access type of the user (owner, edit, view).
"""
# first, check if there's even a board
curB = g.db.execute('select title, created_at, done_at from boards where id=?', [boardID]).fetchone()
if curB is None:
abort(404)
else:
invite = request.args.get('invite') # ?invite=INVITE_ID
auth = is_authorized(boardID)
can_participate = False
done_at = datetime.strptime(curB[2], '%Y-%m-%d %H:%M:%S')
isDone = False
if(done_at < datetime.utcnow()):
isDone = True
if auth['access']: # don't care if there's an invite string as long as you have access while logged in
if auth['accessType'] == 'edit':
can_participate = True
return render_template('show-board.html', canEdit=can_participate, isDone=isDone, title=curB[0], created_at=curB[1], done_at=curB[2], isOwner=auth['isOwner'], boardID=boardID)
elif invite is not None:
cur = g.db.execute('select userEmail, type from invites where id=? and boardID=?', [invite, boardID]).fetchone()
if cur is None:
abort(401)
else:
if cur[1] == 'edit':
can_participate = True
return render_template('show-board.html', canEdit=can_participate, isDone=isDone, title=curB[0], created_at=curB[1], done_at=curB[2], email=cur[0], boardID=boardID)
else:
abort(401)
@app.route('/_removeSelf', methods=['POST'])
[docs]def remove_self():
"""Removes logged in user, who is not the owner of a board, from a certain board they'd been invited to."""
if not session.get('logged_in'):
abort(401)
else:
error = 'None'
person = session['userid']
bid = int(request.form['boardID'])
try:
cur = g.db.execute('select email from users where id=?', [person]).fetchone()
if cur is None:
abort(400)
else:
email = cur[0].lower()
g.db.execute('delete from invites where boardID=? and userEmail=?', [bid, email])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
if(error=='None'):
flash('Successfully removed you from the board.')
else:
flash(error)
return redirect(url_for('index'))
# AJAX functions
## GET
@app.route('/_validateUsername', methods=['GET'])
[docs]def validate_username():
"""If username is available, return true. Else, if taken, return false. Used in registration."""
un = request.args.get('username', 0, type=str)
cur = g.db.execute('select id from users where username=?', [un.lower()]).fetchone()
if cur is None:
return jsonify(available='true')
else:
return jsonify(available='false')
@app.route('/_validateEmail', methods=['GET'])
[docs]def validate_email():
"""If email is available, return true. Else, if taken, return false. Used in registration."""
em = request.args.get('email', 0, type=str)
cur = g.db.execute('select id from users where email=?', [em.lower()]).fetchone()
if cur is None:
return jsonify(available='true')
else:
return jsonify(available='false')
## POST
@app.route('/_editProfile', methods=['POST'])
[docs]def edit_profile():
"""Edit user info, such as username, email, and name for the logged in user."""
if not session.get('logged_in'):
abort(401)
else:
error = 'None'
new_token = generate_csrf_token()
cur = g.db.execute('select name, email, username from users where id=?', [session['userid']]).fetchone()
old_name = cur[0]
old_email = cur[1]
old_username = cur[2]
em = request.form['email'].lower()
un = request.form['username'].lower()
name = request.form['name']
# first, check availability
okay = True
cur1 = g.db.execute('select id from users where email=?', [em]).fetchone()
cur2 = g.db.execute('select id from users where username=?', [un]).fetchone()
if cur1 is not None:
if cur1[0] != session['userid']:
okay = False
error = 'Email is not available.'
if cur2 is not None:
if cur2[0] != session['userid']:
okay = False
if error == 'None':
error = 'Username is not available.'
else:
error+=' Username is not available.'
if okay:
if old_name != name or old_email != em or old_username != un: # only proceed if any changes were made
try:
old_em = g.db.execute('select email from users where id=?', [session['userid']]).fetchone()[0].lower()
g.db.execute('update users set name=?, email=?, username=? where id=?', [name, em, un, session['userid']])
session['username'] = un;
g.db.execute('update invites set userEmail=? where userEmail=?', [em, old_em])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, token=new_token)
@app.route('/_changePassword', methods=['POST'])
[docs]def change_password():
"""Edit password for the logged in user. Front-end handles repeating the password twice before submitting."""
if not session.get('logged_in'):
abort(401)
else:
error = 'None'
new_token = generate_csrf_token()
password = generate_password_hash(request.form['password'], method='pbkdf2:sha512:10000')
pw = g.db.execute('select password from users where id=?', [session['userid']]).fetchone()[0]
if not check_password_hash(pw, request.form['old-password']):
error = 'Old password you entered is incorrect.'
else:
try:
g.db.execute('update users set password=? where id=?', [password, session['userid']])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, token=new_token)
@app.route('/_editInvite', methods=['POST'])
[docs]def edit_invite():
"""Edit the type of invitation/access to invited users. Available only to board owner."""
bid = int(request.form['boardID'])
em = request.form['email']
old_type = request.form['inviteType']
new_token = generate_csrf_token()
error = 'None'
if not session.get('logged_in') or not is_owner(bid, session['userid']):
abort(401)
else:
if old_type=='edit':
new_type = 'view'
elif old_type=='view':
new_type = 'edit'
else:
abort(400)
try:
g.db.execute('update invites set type=? where boardID=? and userEmail=?', [new_type, bid, em])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, token=new_token)
## API (GET/POST)
@app.route('/api/invite/user/<email>/board/<boardID>', methods=['POST'])
[docs]def invite_user(email, boardID):
"""Invite a user to a board via their email. Available only to the board owner."""
if not session.get('logged_in'):
abort(401)
em = email.lower()
ty = request.form['type'] # view or edit
bid = int(boardID)
user = session['userid']
inviteID = uuid.uuid4().hex
error = 'None'
successful='false'
if is_owner(bid, user):
try:
g.db.execute('insert into invites (id, userEmail, boardID, type) values (?, ?, ?, ?)', [inviteID, em, bid, ty])
g.db.commit()
successful = 'true'
except sqlite3.IntegrityError as e:
error = 'This email has already been invited to this board.'
except sqlite3.Error as e: # for debugging
error = e.args[0]
finally:
new_token = generate_csrf_token()
return jsonify(successful=successful, error=error, token=new_token)
@app.route('/api/edit/board/<boardID>/title', methods=['POST'])
[docs]def edit_board(boardID):
"""Edit board title if the user is the owner."""
if not session.get('logged_in'):
abort(401)
else: # is logged in
curBoard = g.db.execute('select title from boards where id=?', [int(boardID)]).fetchone()
if curBoard is None:
abort(404)
error = 'None'
new_token = generate_csrf_token()
try:
g.db.execute('update boards set title=? where id=? and creatorID=?', [request.form['title'], int(boardID), session['userid']])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, token=new_token) # and new CSRF token to be used again
@app.route('/api/expire/board/<boardID>', methods=['POST'])
[docs]def mark_done(boardID):
"""Mark a board as done before the 24 hours are up. Only available to the owner of the board."""
if not session.get('logged_in'):
abort(401)
else:
error = 'None'
new_token = generate_csrf_token()
done = datetime.utcnow()
done_at = done.strftime('%Y-%m-%d %H:%M:%S')
bid = int(boardID)
old_done_at = datetime.strptime(g.db.execute('select done_at from boards where id=?', [bid]).fetchone()[0], '%Y-%m-%d %H:%M:%S')
if old_done_at < done:
abort(400)
try:
g.db.execute('update boards set done_at=? where id=? and creatorID=?', [done_at, bid, session['userid']])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, token=new_token)
@app.route('/api/delete/board/<boardID>', methods=['POST'])
[docs]def delete_board(boardID):
"""Delete board. Only allows it in case the board in question"""
if not session.get('logged_in'):
abort(401)
else:
error = 'None'
new_token = generate_csrf_token()
bid = int(boardID)
try:
g.db.execute('delete from boards where id=? and creatorID=?', [bid, session['userid']])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
if(error!='None'):
flash(error)
return redirect(url_for('show_profile'))
@app.route('/api/board/<boardID>/components/get', methods=['GET'])
[docs]def get_components(boardID):
"""Get all components of a board. This includes:
- Chat, text, and other components along with all their relevant data (date, who, etc).
- State of the board: locked/unlocked.
"""
bid = int(boardID)
curBoard = g.db.execute('select locked_until, locked_by from boards where id=?', [bid]).fetchone()
if curBoard is None:
abort(404)
else:
inv = request.args.get('invite', 0, str)
if inv != '-1' and not session.get('logged_in'):
curInvite = g.db.execute('select userEmail from invites where id=?', [inv]).fetchone()
if curInvite is None:
abort(401)
else:
who = curInvite[0]
elif session.get('logged_in'):
auth = is_authorized(bid)
if not auth['access']:
abort(401)
else:
who = str(session['userid'])
else:
abort(401)
lastClientGot = request.args.get('lastModified', 0, str)
lock_until = datetime.strptime(curBoard[0], '%Y-%m-%d %H:%M:%S')
lock_by = curBoard[1]
LOCKED = False
if datetime.utcnow() < lock_until and lock_by != who:
LOCKED = True
# get list
try:
curList = g.db.execute('select id, content, userID, userEmail, created_at, last_modified_at, last_modified_by, type, position, deleted from board_content where boardID=? and last_modified_at > ? order by created_at', [bid, lastClientGot]).fetchall()
if len(curList) > 0:
messages = [dict(id=row[0], content=row[1], userID=row[2], userEmail=row[3], created_at=row[4], last_modified_at=row[5], last_modified_by=row[6], type=row[7], position=row[8], deleted=row[9]) for row in curList]
return jsonify(messages=messages, locked=LOCKED, lockedBy=lock_by)
else:
error = 'Nothing new.'
return jsonify(error=error, locked=LOCKED, lockedBy=lock_by)
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, locked=LOCKED, lockedBy=lock_by)
@app.route('/api/board/<boardID>/components/post', methods=['POST'])
[docs]def post_components(boardID):
"""Post a component to the board. Works for all types."""
bid = int(boardID)
new_token = generate_csrf_token()
msg = request.form['message']
ty = request.form['content-type']
position = request.form['position']
error = 'None'
componentID = None
wantEdit = False if ty == 'chat' else True
curBoard = g.db.execute('select done_at, locked_until, locked_by from boards where id=?', [bid]).fetchone()
if curBoard is None:
abort(404)
done_at = datetime.strptime(curBoard[0], '%Y-%m-%d %H:%M:%S')
inv = request.form['invite']
if inv != '-1' and not session.get('logged_in'):
curInvite = g.db.execute('select type, userEmail from invites where id=? and boardID=?', [inv, bid]).fetchone()
if curInvite is None:
abort(401)
else:
who = curInvite[1]
elif session.get('logged_in'):
auth = is_authorized(bid, wantToEdit=wantEdit)
if not auth['access'] and auth['accessType'] == 'edit':
abort(401)
who = session['userid']
if(done_at > datetime.utcnow()):
if len(msg)>=1:
if session.get('logged_in'):
if (ty != 'chat' and auth['canEditNow']) or (ty == 'chat'):
try:
cursor = g.db.cursor()
cursor.execute('insert into board_content (boardID, userID, content, type, position, last_modified_at, last_modified_by) values (?, ?, ?, ?, ?, ?, ?)', [bid, who, msg, ty, position, datetime.utcnow(), who])
g.db.commit()
componentID = cursor.lastrowid
cursor.close()
except sqlite3.Error as e:
error = e.args[0]
else:
error = 'This board is locked for edit by another user.'
elif inv != '-1' and ((curInvite[0] == 'edit' and ty != 'chat') or (ty == 'chat')):
lockedUntil = datetime.strptime(curBoard[1],'%Y-%m-%d %H:%M:%S')
lockedBy = curBoard[2]
allowEdit = False
if datetime.utcnow() > lockedUntil or (datetime.utcnow() < lockedUntil and lockedBy == who):
try:
lock_board(bid, userEmail=who)
allowEdit = True
except sqlite3.Error as e:
error = e.args[0]
if allowEdit:
try:
cursor = g.db.cursor()
cursor.execute('insert into board_content (boardID, userEmail, content, type, position, last_modified_at, last_modified_by) values (?, ?, ?, ?, ?, ?, ?)', [bid, who, msg, ty, position, datetime.utcnow(), who])
g.db.commit()
cursor.close()
except sqlite3.Error as e:
error = e.args[0]
else:
error = 'This board is locked for edit by another user.'
else:
error = 'Your priviliges do not allow you to post to this board.'
else:
error = 'Content too short.'
else:
error = 'This board has expired. You cannot make any changes.'
return jsonify(error=error, token=new_token, componentID=componentID)
@app.route('/api/user/<userID>', methods=['GET'])
[docs]def get_user(userID):
"""Get a user's username/name based on their ID. Available to everyone."""
# no authentication needed, public info. A better app would only provide this info to people who have something in common with the user
## like they share a board. But for now, it's just public.
error = 'None'
username = None
name = None
try:
cur = g.db.execute('select name, username from users where id=?', [int(userID)]).fetchone()
if cur is None:
error = 'User not found.'
else:
name = cur[0]
username = cur[1]
except sqlite3.Error as e: # just in case
error = e.args[0]
return jsonify(error=error, username=username, name=name)
@app.route('/api/invited/<boardID>', methods=['GET'])
[docs]def invited_users(boardID):
"""Get list of invited emails and the type of their invitations. Available to board owner only."""
error = 'None'
invited = 'None'
new_token = generate_csrf_token() # for the POST forms generated on the fly
bid = int(boardID)
curBoard = g.db.execute('select title from boards where id=?', [bid]).fetchone()
if curBoard is None:
abort(404)
if not session.get('logged_in') or not is_owner(bid, session['userid']):
abort(401)
else:
try:
cur = g.db.execute('select userEmail, type from invites where boardID=? order by invite_date', [bid]).fetchall()
if len(cur) == 0:
error = 'No one has been invited to this board yet.'
else:
invited = [dict(userEmail=row[0], type=row[1]) for row in cur]
except sqlite3.Error as e:
error = e.args[0]
return jsonify(error=error, invited=invited, token=new_token)
@app.route('/api/edit/board/<boardID>/component/<componentID>', methods=['POST'])
[docs]def edit_component(componentID, boardID):
"""Edit a component's content (the text, etc). Available to anyone with edit/owner access to the board."""
error = 'None'
new_token = generate_csrf_token()
bid = int(boardID)
cid = int(componentID)
inv = request.form['invite']
curBoard = g.db.execute('select locked_until, locked_by from boards where id=?', [bid]).fetchone()
if curBoard is None:
abort(404)
if inv != '-1' and not session.get('logged_in'): # don't care if there's an invite string as long as they're logged in
cur = g.db.execute('select type, userEmail from invites where id=? and boardID=?', [inv, bid]).fetchone()
if cur is None:
abort(401)
elif cur[0] != 'edit':
abort(401)
else:
mod = cur[1]
lockedUntil = datetime.strptime(curBoard[0], '%Y-%m-%d %H:%M:%S')
lockedBy = curBoard[1]
if(datetime.utcnow() > lockedUntil) or (datetime.utcnow() < lockedUntil and lockedBy == mod):
try:
lock_board(bid,userEmail=mod)
allowEdit = True
except sqlite3.Error as e:
error = e.args[0]
elif session.get('logged_in'):
auth = is_authorized(bid, wantToEdit=True)
if not auth['access'] or not auth['accessType'] == 'edit' or not auth['canEditNow']:
abort(401)
else:
allowEdit = True
mod = session['userid']
else:
abort(401)
# if we get this far, user has editing access
if allowEdit:
ty = request.form['content-type']
nowDate = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
curDone = g.db.execute('select done_at from boards where id=?', [bid]).fetchone()
done_at = datetime.strptime(curDone[0], '%Y-%m-%d %H:%M:%S')
if done_at > datetime.utcnow():
try:
if request.form['hasMessages']=='true':
msg = request.form['message']
if len(msg)>=1:
g.db.execute('update board_content set content=?, last_modified_at=?, last_modified_by=? where id=? and boardID=? and type=? and deleted=?', [msg, nowDate, mod, cid, bid, ty, 'N'])
g.db.commit()
else:
error = 'Content too short.'
else: # refreshing position only
pos = request.form['position']
g.db.execute('update board_content set position=?, last_modified_at=?, last_modified_by=? where id=? and boardID=? and type=? and deleted=?', [pos, nowDate, mod, cid, bid, ty, 'N'])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
else:
error = 'This board has expired. You cannot make any more changes.'
else:
error = 'This board is locked for edit by another user.'
return jsonify(error=error, token=new_token)
@app.route('/api/delete/board/<boardID>/component/<componentID>', methods=['POST'])
[docs]def delete_component(boardID, componentID):
"""Delete a component. Separated from editing for readability and possible future modification of the edit function. Also available to everyone with edit/owner access."""
error = 'None'
new_token = generate_csrf_token()
bid = int(boardID)
cid = int(componentID)
inv = request.form['invite']
curBoard = g.db.execute('select locked_until, locked_by from boards where id=?', [bid]).fetchone()
if curBoard is None:
abort(404)
if inv != '-1' and not session.get('logged_in'): # don't care if there's an invite string as long as they're logged in
cur = g.db.execute('select type, userEmail from invites where id=? and boardID=?', [inv, bid]).fetchone()
if cur is None:
abort(401)
elif cur[0] != 'edit':
abort(401)
else:
mod = cur[1]
lockedUntil = datetime.strptime(curBoard[0], '%Y-%m-%d %H:%M:%S')
lockedBy = curBoard[1]
if(datetime.utcnow() > lockedUntil) or (datetime.utcnow() < lockedUntil and lockedBy == mod):
try:
lock_board(bid,userEmail=mod)
allowDelete = True
except sqlite3.Error as e:
error = e.args[0]
elif session.get('logged_in'):
auth = is_authorized(bid, wantToEdit=True)
if not auth['access'] or not auth['accessType'] == 'edit' or not auth['canEditNow']:
abort(401)
else:
allowDelete = True
mod = session['userid']
else:
abort(401)
if allowDelete:
nowDate = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
curDone = g.db.execute('select done_at from boards where id=?', [bid]).fetchone()
done_at = datetime.strptime(curDone[0], '%Y-%m-%d %H:%M:%S')
if done_at > datetime.utcnow():
try:
g.db.execute('update board_content set deleted=?, last_modified_at=?, last_modified_by=? where id=? and boardID=? and type!=?', ['Y', nowDate, mod, cid, bid, 'chat'])
g.db.commit()
except sqlite3.Error as e:
error = e.args[0]
else:
error = 'This board has expired. You cannot make any more changes.'
else:
error = 'This board is locked for edit by another user.'
return jsonify(error=error, token=new_token)