re-add api route

This commit is contained in:
Lera Elvoé 2025-07-02 13:35:14 +03:00
parent 3c3837b3f2
commit 64e18f16dd
Signed by: yagich
SSH Key Fingerprint: SHA256:6xjGb6uA7lAVcULa7byPEN//rQ0wPoG+UzYVMfZnbvc
4 changed files with 85 additions and 3 deletions

View File

@ -76,11 +76,13 @@ def create_app():
from app.routes.threads import bp as threads_bp from app.routes.threads import bp as threads_bp
from app.routes.users import bp as users_bp from app.routes.users import bp as users_bp
from app.routes.mod import bp as mod_bp from app.routes.mod import bp as mod_bp
from app.routes.api import bp as api_bp
app.register_blueprint(app_bp) app.register_blueprint(app_bp)
app.register_blueprint(topics_bp) app.register_blueprint(topics_bp)
app.register_blueprint(threads_bp) app.register_blueprint(threads_bp)
app.register_blueprint(users_bp) app.register_blueprint(users_bp)
app.register_blueprint(mod_bp) app.register_blueprint(mod_bp)
app.register_blueprint(api_bp)
app.config['SESSION_COOKIE_SECURE'] = True app.config['SESSION_COOKIE_SECURE'] = True
@ -88,11 +90,12 @@ def create_app():
def make_session_permanent(): def make_session_permanent():
session.permanent = True session.permanent = True
@app.context_processor
def inject_constants():
commit = "" commit = ""
with open('.git/refs/heads/main') as f: with open('.git/refs/heads/main') as f:
commit = f.read().strip() commit = f.read().strip()
@app.context_processor
def inject_constants():
return { return {
"InfoboxIcons": InfoboxIcons, "InfoboxIcons": InfoboxIcons,
"InfoboxHTMLClass": InfoboxHTMLClass, "InfoboxHTMLClass": InfoboxHTMLClass,

View File

@ -1,5 +1,6 @@
from .db import Model, db from .db import Model, db
from .constants import PermissionLevel from .constants import PermissionLevel
import time
class Users(Model): class Users(Model):
table = "users" table = "users"
@ -219,3 +220,27 @@ class Avatars(Model):
class Subscriptions(Model): class Subscriptions(Model):
table = "subscriptions" table = "subscriptions"
class APIRateLimits(Model):
table = 'api_rate_limits'
@classmethod
def is_allowed(cls, user_id, method, seconds):
q = """
SELECT logged_at FROM api_rate_limits
WHERE user_id = ? AND method = ?
ORDER BY logged_at DESC LIMIT 1"""
last_call = db.fetch_one(q, user_id, method)
if last_call is None or (int(time.time()) - int(last_call['logged_at']) >= seconds):
with db.transaction():
db.query(
'DELETE FROM api_rate_limits WHERE user_id = ? AND method = ?',
user_id, method
)
db.query(
'INSERT INTO api_rate_limits (user_id, method) VALUES (?, ?)',
user_id, method
)
return True
else:
return False

43
app/routes/api.py Normal file
View File

@ -0,0 +1,43 @@
from flask import Blueprint, request, url_for
from ..lib.babycode import babycode_to_html
from .users import is_logged_in, get_active_user
from ..models import APIRateLimits, Threads
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}

View File

@ -72,5 +72,16 @@
</span> </span>
</div> </div>
</dialog> </dialog>
<input type='hidden' id='thread-subscribe-endpoint' value='{{ url_for('api.thread_updates', thread_id=thread.id) }}'>
<div id="new-post-notification" class="new-concept-notification hidden">
<div class="new-notification-content">
<p>New post in thread!</p>
<span class="notification-buttons">
<button id="dismiss-new-post-button">Dismiss</button>
<a class="linkbutton" id="go-to-new-post-button">View post</a>
<button id="unsub-new-post-button">Stop updates</button>
</span>
</div>
</div>
<script src="/static/js/thread.js?v=1"></script> <script src="/static/js/thread.js?v=1"></script>
{% endblock %} {% endblock %}