Files
pyrom/app/routes/api.py

215 lines
6.8 KiB
Python

from flask import Blueprint, request, url_for
from ..lib.babycode import babycode_to_html
from ..constants import REACTION_EMOJI
from .users import is_logged_in, get_active_user
from ..models import APIRateLimits, Threads, Reactions, Users, BookmarkCollections, BookmarkedThreads, BookmarkedPosts
from ..db import db
bp = Blueprint("api", __name__, url_prefix="/api/")
@bp.post('/thread-updates/<thread_id>')
def thread_updates(thread_id):
thread = Threads.find({'id': thread_id})
if not thread:
return {'error': 'no such thread'}, 404
target_time = request.json.get('since')
if not target_time:
return {'error': 'missing parameter "since"'}, 400
try:
target_time = int(target_time)
except:
return {'error': 'parameter "since" is not/cannot be converted to a number'}, 400
q = 'SELECT id FROM posts WHERE thread_id = ? AND posts.created_at > ? ORDER BY posts.created_at ASC LIMIT 1'
new_post = db.fetch_one(q, thread_id, target_time)
if not new_post:
return {'status': 'none'}
url = url_for('threads.thread', slug=thread.slug, after=new_post['id'], _anchor=f"post-{new_post['id']}")
return {'status': 'new_post', 'url': url}
@bp.post('/babycode-preview')
def babycode_preview():
if not is_logged_in():
return {'error': 'not authorized'}, 401
user = get_active_user()
if not APIRateLimits.is_allowed(user.id, 'babycode_preview', 5):
return {'error': 'too many requests'}, 429
markup = request.json.get('markup')
if not markup or not isinstance(markup, str):
return {'error': 'markup field missing or invalid type'}, 400
rendered = babycode_to_html(markup)
return {'html': rendered}
@bp.post('/add-reaction/<post_id>')
def add_reaction(post_id):
if not is_logged_in():
return {'error': 'not authorized', 'error_code': 401}, 401
user = get_active_user()
reaction_text = request.json.get('emoji')
if not reaction_text or not isinstance(reaction_text, str):
return {'error': 'emoji field missing or invalid type', 'error_code': 400}, 400
if reaction_text not in REACTION_EMOJI:
return {'error': 'unsupported reaction', 'error_code': 400}, 400
reaction = Reactions.find({
'user_id': user.id,
'post_id': int(post_id),
'reaction_text': reaction_text,
})
if reaction:
return {'error': 'reaction already exists', 'error_code': 409}, 409
reaction = Reactions.create({
'user_id': user.id,
'post_id': int(post_id),
'reaction_text': reaction_text,
})
return {'status': 'added'}
@bp.post('/remove-reaction/<post_id>')
def remove_reaction(post_id):
if not is_logged_in():
return {'error': 'not authorized'}, 401
user = get_active_user()
reaction_text = request.json.get('emoji')
if not reaction_text or not isinstance(reaction_text, str):
return {'error': 'emoji field missing or invalid type'}, 400
if reaction_text not in REACTION_EMOJI:
return {'error': 'unsupported reaction'}, 400
reaction = Reactions.find({
'user_id': user.id,
'post_id': int(post_id),
'reaction_text': reaction_text,
})
if not reaction:
return {'error': 'reaction does not exist'}, 404
reaction.delete()
return {'status': 'removed'}
@bp.post('/manage-bookmark-collections/<user_id>')
def manage_bookmark_collections(user_id):
if not is_logged_in():
return {'error': 'not authorized', 'error_code': 401}, 401
target_user = Users.find({'id': user_id})
if target_user.id != get_active_user().id:
return {'error': 'forbidden', 'error_code': 403}, 403
if target_user.is_guest():
return {'error': 'forbidden', 'error_code': 403}, 403
collections_data = request.json
for idx, coll_data in enumerate(collections_data.get('collections')):
if coll_data['is_new']:
collection = BookmarkCollections.create({
'name': coll_data['name'],
'user_id': target_user.id,
'sort_order': idx,
})
else:
collection = BookmarkCollections.find({'id': coll_data['id']})
if not collection:
continue
update = {'name': coll_data['name']}
if not collection.is_default:
update['sort_order'] = idx
collection.update(update)
for removed_id in collections_data.get('removed_collections'):
collection = BookmarkCollections.find({'id': removed_id})
if not collection:
continue
if collection.is_default:
continue
collection.delete()
return {'status': 'ok'}, 200
@bp.post('/bookmark-post/<post_id>')
def bookmark_post(post_id):
if not is_logged_in():
return {'error': 'not authorized', 'error_code': 401}, 401
operation = request.json.get('operation')
if operation == 'remove' and request.json.get('collection_id', '') == '':
return {'status': 'not modified'}, 304
collection_id = int(request.json.get('collection_id'))
post_id = int(post_id)
memo = request.json.get('memo', '')
if operation == 'move':
bm = BookmarkedPosts.find({'post_id': post_id})
if not bm:
BookmarkedPosts.create({
'post_id': post_id,
'collection_id': collection_id,
'note': memo,
})
else:
bm.update({
'collection_id': collection_id,
'note': memo,
})
elif operation == 'remove':
bm = BookmarkedPosts.find({'post_id': post_id})
if bm:
bm.delete()
else:
return {'error': 'bad request'}, 400
return {'status': 'ok'}, 200
@bp.post('/bookmark-thread/<thread_id>')
def bookmark_thread(thread_id):
if not is_logged_in():
return {'error': 'not authorized', 'error_code': 401}, 401
operation = request.json.get('operation')
if operation == 'remove' and request.json.get('collection_id', '') == '':
return {'status': 'not modified'}, 304
collection_id = int(request.json.get('collection_id'))
thread_id = int(thread_id)
memo = request.json.get('memo', '')
if operation == 'move':
bm = BookmarkedThreads.find({'thread_id': thread_id})
if not bm:
BookmarkedThreads.create({
'thread_id': thread_id,
'collection_id': collection_id,
'note': memo,
})
else:
bm.update({
'collection_id': collection_id,
'note': memo,
})
elif operation == 'remove':
bm = BookmarkedThreads.find({
'thread_id': thread_id,
'note': memo,
})
if bm:
bm.delete()
else:
return {'error': 'bad request'}, 400
return {'status': 'ok'}, 200