start the new topics route and view

This commit is contained in:
2026-04-12 23:36:50 +03:00
parent 099b5c135e
commit ce9bca0a75
11 changed files with 141 additions and 34 deletions

View File

@@ -1,7 +1,7 @@
from flask import Flask, session, request, render_template from flask import Flask, session, request, render_template
from dotenv import load_dotenv from dotenv import load_dotenv
from .models import Avatars, Users, PostHistory, Posts, MOTD, BadgeUploads, Sessions from .models import Avatars, Users, PostHistory, Posts, MOTD, BadgeUploads, Sessions
from .auth import digest from .auth import digest, is_logged_in
from .constants import ( from .constants import (
PermissionLevel, permission_level_string, PermissionLevel, permission_level_string,
InfoboxKind, InfoboxHTMLClass, InfoboxKind, InfoboxHTMLClass,
@@ -221,6 +221,11 @@ def create_app():
with open('.git/refs/heads/main') as f: with open('.git/refs/heads/main') as f:
commit = f.read().strip() commit = f.read().strip()
from app.routes.app import bp as app_bp
from app.routes.topics import bp as topics_bp
app.register_blueprint(app_bp)
app.register_blueprint(topics_bp)
@app.context_processor @app.context_processor
def inject_constants(): def inject_constants():
return { return {
@@ -239,6 +244,7 @@ def create_app():
return { return {
'get_motds': MOTD.get_all, 'get_motds': MOTD.get_all,
'get_time_now': lambda: int(time.time()), 'get_time_now': lambda: int(time.time()),
'is_logged_in': is_logged_in,
} }
@app.template_filter('ts_datetime') @app.template_filter('ts_datetime')
@@ -268,23 +274,23 @@ def create_app():
def basename_noext(subj): def basename_noext(subj):
return os.path.splitext(os.path.basename(subj))[0] return os.path.splitext(os.path.basename(subj))[0]
@app.errorhandler(404) # @app.errorhandler(404)
def _handle_404(e): # def _handle_404(e):
if request.path.startswith('/hyperapi/'): # if request.path.startswith('/hyperapi/'):
return '<h1>not found</h1>', e.code # return '<h1>not found</h1>', e.code
elif request.path.startswith('/api/'): # elif request.path.startswith('/api/'):
return {'error': 'not found'}, e.code # return {'error': 'not found'}, e.code
else: # else:
return render_template('common/404.html'), e.code # return render_template('common/404.html'), e.code
#
@app.errorhandler(413) # @app.errorhandler(413)
def _handle_413(e): # def _handle_413(e):
if request.path.startswith('/hyperapi/'): # if request.path.startswith('/hyperapi/'):
return '<h1>request body too large</h1>', e.code # return '<h1>request body too large</h1>', e.code
elif request.path.startswith('/api/'): # elif request.path.startswith('/api/'):
return {'error': 'body too large'}, e.code # return {'error': 'body too large'}, e.code
else: # else:
return render_template('common/413.html'), e.code # return render_template('common/413.html'), e.code
# this only happens at build time but # this only happens at build time but
# build time is when updates are done anyway # build time is when updates are done anyway

View File

@@ -1,4 +1,7 @@
from flask import session, flash
from .models import Sessions
from argon2 import PasswordHasher from argon2 import PasswordHasher
import time
ph = PasswordHasher() ph = PasswordHasher()
@@ -10,3 +13,16 @@ def verify(expected, given):
return ph.verify(expected, given) return ph.verify(expected, given)
except: except:
return False return False
def is_logged_in():
if "pyrom_session_key" not in session:
return False
sess = Sessions.find({"key": session["pyrom_session_key"]})
if not sess:
return False
if sess.expires_at < int(time.time()):
session.clear()
sess.delete()
# flash('Your session expired.;Please log in again.', InfoboxKind.INFO)
return False
return True

View File

@@ -111,24 +111,16 @@ class Topics(Model):
q = """ q = """
SELECT SELECT
topics.id, topics.name, topics.slug, topics.description, topics.is_locked, topics.id, topics.name, topics.slug, topics.description, topics.is_locked,
users.username AS latest_thread_username, COUNT(DISTINCT threads.id) as threads_count,
users.display_name AS latest_thread_display_name, COUNT(posts.id) AS posts_count,
threads.title AS latest_thread_title, MAX(posts.created_at) as latest_post_timestamp
threads.slug AS latest_thread_slug,
threads.created_at AS latest_thread_created_at
FROM FROM
topics topics
LEFT JOIN (
SELECT
*,
row_number() OVER (PARTITION BY threads.topic_id ORDER BY threads.created_at DESC) as rn
FROM
threads
) threads ON threads.topic_id = topics.id AND threads.rn = 1
LEFT JOIN LEFT JOIN
users on users.id = threads.user_id threads ON threads.topic_id = topics.id
ORDER BY LEFT JOIN
topics.sort_order ASC""" posts ON posts.thread_id = threads.id
GROUP BY topics.id ORDER BY topics.sort_order ASC"""
return db.query(q) return db.query(q)
def get_threads(self, per_page, page, sort_by = 'activity'): def get_threads(self, per_page, page, sort_by = 'activity'):

6
app/routes/app.py Normal file
View File

@@ -0,0 +1,6 @@
from flask import Blueprint, redirect, url_for, render_template
bp = Blueprint('app', __name__, url_prefix = '/')
@bp.get('/')
def index():
return redirect(url_for('topics.all_topics'))

17
app/routes/topics.py Normal file
View File

@@ -0,0 +1,17 @@
from flask import Blueprint, redirect, url_for, render_template
from ..models import Topics
bp = Blueprint('topics', __name__, url_prefix = '/topics/')
@bp.get('/')
def all_topics():
topic_list = Topics.get_list()
return render_template('topics/topics.html', topics=topic_list)
@bp.get('/<slug>')
def topic(slug):
t = Topics.find({'slug': slug})
if t:
return 'yes'
return 'no'

21
app/templates/base.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/static/favicon.png">
<link rel="stylesheet" href="{{ "/static/css/style.css" | cachebust }}">
{% if self.title() -%}
<title>{{ config.SITE_NAME }} - {% block title -%}{%- endblock -%}</title>
{%- else -%}
<title>{{ config.SITE_NAME }}</title>
{%- endif -%}
</head>
<body>
<div id="wrapper">
{%- include 'common/topnav.html' -%}
{%- block content -%}{%- endblock -%}
{%- include 'common/footer.html' -%}
</div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
<footer class="plank secondary-bg bottom">
<span>Pyrom commit <a href="{{ "https://git.poto.cafe/yagich/pyrom/commit/" + __commit }}">{{ __commit[:8] }}</a></span>
<ul class="horizontal">
{#-
<li><a href="#">Contact</a></li>
<li><a href="#">Guides</a></li>
-#}
</ul>
</footer>

View File

@@ -0,0 +1,3 @@
{% macro timestamp(unix_ts) -%}
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} <abbr title="Server Time">ST</abbr></span>
{%- endmacro %}

View File

@@ -0,0 +1,15 @@
<nav id="header" class="plank top">
<a class="site-title" href=#>Porom</a>
<span>anti-social media</span>
{%- if is_logged_in() -%}
no
{%- else -%}
<form class="horizontal wrap">
<input type="hidden" name="return_to" value="{{request.path}}">
<input type="text" placeholder="Username">
<input type="password" placeholder="Password">
<input type="submit" value="Log in">
<a href="#" class="linkbutton">Register</a>
</form>
{%- endif -%}
</nav>

View File

@@ -0,0 +1,23 @@
{% from 'common/macros.html' import timestamp %}
{%- extends 'base.html' -%}
{%- block content -%}
{%- for topic in topics -%}
<div class="topic-info plank">
<div class="title-container">
<a class="info" href="{{url_for('topics.topic', slug=topic.slug)}}">{{topic.name}}</a>
</div>
<div>{{topic.description}}</div>
<ul class="horizontal">
<li>{{topic.threads_count}} {{"thread" | pluralize(topic.threads_count)}}</li>
<li>{{topic.posts_count}} {{"post" | pluralize(topic.posts_count)}}</li>
</ul>
<div>
{%- if topic.latest_post_timestamp -%}
Latest post at: {{timestamp(topic.latest_post_timestamp)}}
{%- else -%}
No posts yet
{%- endif -%}
</div>
</div>
{%- endfor -%}
{%- endblock -%}

View File

@@ -76,7 +76,6 @@ body {
--medium-padding: calc(var(--base-padding) * 2); --medium-padding: calc(var(--base-padding) * 2);
--big-padding: calc(var(--base-padding) * 3); --big-padding: calc(var(--base-padding) * 3);
--huge-padding: calc(var(--base-padding) * 4); --huge-padding: calc(var(--base-padding) * 4);
--input-height: calc(var(--base-padding) * 6);
--code-bg-color: hsl(from var(--bg-color-primary) h calc(s * 0.2) calc(l * 0.2)); --code-bg-color: hsl(from var(--bg-color-primary) h calc(s * 0.2) calc(l * 0.2));