Compare commits
3 Commits
57a6810b03
...
3da3054587
Author | SHA1 | Date | |
---|---|---|---|
3da3054587 | |||
64e18f16dd | |||
3c3837b3f2 |
@ -53,7 +53,6 @@ $ deactivate
|
|||||||
|
|
||||||
when you want to run the server again, make sure to activate the venv first:
|
when you want to run the server again, make sure to activate the venv first:
|
||||||
```bash
|
```bash
|
||||||
$ python -m venv .venv
|
|
||||||
$ source .venv/bin/activate
|
$ source .venv/bin/activate
|
||||||
$ python -m app.run
|
$ python -m app.run
|
||||||
```
|
```
|
||||||
|
@ -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,
|
||||||
|
@ -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
43
app/routes/api.py
Normal 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}
|
@ -31,7 +31,6 @@ def thread(slug):
|
|||||||
("thread_id", "=", thread.id),
|
("thread_id", "=", thread.id),
|
||||||
("id", "<=", after_id),
|
("id", "<=", after_id),
|
||||||
])
|
])
|
||||||
print(post_position)
|
|
||||||
page = math.ceil((post_position) / POSTS_PER_PAGE)
|
page = math.ceil((post_position) / POSTS_PER_PAGE)
|
||||||
else:
|
else:
|
||||||
page = max(1, min(page_count, int(request.args.get("page", default = 1))))
|
page = max(1, min(page_count, int(request.args.get("page", default = 1))))
|
||||||
|
@ -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 %}
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
function tryFetchUpdate() {
|
function tryFetchUpdate() {
|
||||||
if (!threadEndpoint) return;
|
if (!threadEndpoint) return;
|
||||||
const body = JSON.stringify({since: now});
|
const body = JSON.stringify({'since': now});
|
||||||
fetch(threadEndpoint, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
fetch(threadEndpoint, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
|
Loading…
Reference in New Issue
Block a user