Compare commits

...

6 Commits

6 changed files with 129 additions and 27 deletions

View File

@@ -1,18 +1,70 @@
# Pyrom
python/flask port of [porom](https://git.poto.cafe/yagich/porom)
pyrom is a playful home-grown forum software for the indie web borne out of frustration with social media and modern forums imitating it.
this is now the canonical implementation of porom. it's compatible with the database of porom.
the aim is not to recreate the feeling of forums from any time period. rather, it aims to serve as a lightweight alternative to other forum software packages. pyrom is lean and "fire-and-forget"; there is little necessary configuration, making it a great fit for smaller communities (though nothing prevents it from being used in larger ones.)
# License
Released under [CNPLv7+](https://thufie.lain.haus/NPL.html).
Please read the [full terms](./LICENSE.md) for proper wording.
a live example can be seen in action over at [Porom](https://forum.poto.cafe/).
## stack & structure
on the server side, pyrom is built in Python using the Flask framework. content is rendered mostly server-side with Jinja templates. the database used is SQLite.
on the client side, JS with only one library ([Bitty](https://bitty-js.com)) is used. for CSS, pyrom uses Sass.
below is an explanation of the folder structure:
- `/`
- `app/`
- `lib/` - utility libraries
- `routes/` - each `.py` file represents a "sub-app", usually the first part of the URL
- `templates/` - Jinja templates used by the routes. each subfolder corresponds to the "sub-app" that uses that template.
- `__init__.py` - creates the app
- `auth.py` - authentication helper
- `constants.py` - constant values used throughout the forum
- `db.py` - database abstraction layer and ORM library
- `migrations.py` - database migrations
- `models.py` - ORM model definitions
- `run.py` - runner script for development
- `schema.py` - database schema definition
- `config/` - configuration for the forum
- `data/`
- `_cached/` - cached versions of certain endpoints are stored here
- `db/` - the SQLite database is stored here
- `static/` - static files
- `avatars/` - user avatar uploads
- `badges/` - user badge uploads
- `css/` - CSS files generated from Sass sources
- `emoji/` - emoji images used on the forum
- `fonts/`
- `js/`
- `sass/`
- `_default.scss` - the default theme. Sass variables that other themes modify are defined here, along with the default styles. other files define the available themes.
- `build-themes.sh` - script for building Sass files into CSS
- `nginx.conf` - nginx config (production only)
- `uwsgi.ini` - uwsgi config (production only)
# license
released under [CNPLv7+](https://thufie.lain.haus/NPL.html).
please read the [full terms](./LICENSE.md) for proper wording.
# acknowledgments
pyrom uses many open-source and otherwise free-culture components. see the [THIRDPARTY](./THIRDPARTY.md) file for full credit.
# installing & first time setup
## docker (production)
create `config/secrets.prod.env` according to `config/secrets.prod.env.example`
1. clone the repo
2. create `config/secrets.prod.env` according to `config/secrets.prod.env.example`
3. create `config/pyrom_config.toml` according to `config/pyrom_config.toml.example` and modify as needed
4. make sure the `data/` folder is writable by the app:
```bash
$ docker compose up
$ chmod -R 777 data/
```
5. bring up the container:
```bash
$ docker compose up --build
```
- opens port 8080
@@ -20,10 +72,10 @@ $ docker compose up
make sure to run it in an interactive session the first time, because it will spit out the password to the auto-created admin account.
alternatively, if you already had porom running before, put the db file (`db.prod.sqlite`) in `data/db` and it will Just Work.
6. point your favorite proxy at `localhost:8080`
## manual (development)
1. install python >= 3.11, sqlite3, libargon2, and imagemagick & clone repo
1. install python >= 3.13, sqlite3, libargon2, and imagemagick & clone repo
2. create a venv:
```bash
@@ -59,6 +111,3 @@ $ source .venv/bin/activate
$ python -m app.run
```
# acknowledgments
pyrom uses many open-source and otherwise free-culture components. see the [THIRDPARTY](./THIRDPARTY.md) file for full credit.

View File

@@ -85,3 +85,18 @@ URL: https://bitty-js.com/
License: CC0 1.0
Author: alan w smith https://www.alanwsmith.com/
Repo: https://github.com/alanwsmith/bitty
## Flask-Caching
URL: https://flask-caching.readthedocs.io/
Copyright:
```
Copyright (c) 2010 by Thadeus Burgess.
Copyright (c) 2016 by Peter Justin.
Some rights reserved.
```
License: BSD-3-Clause ([see more](https://github.com/pallets-eco/flask-caching/blob/e59bc040cd47cd2b43e501d636d43d442c50b3ff/LICENSE))
Repo: https://github.com/pallets-eco/flask-caching

View File

@@ -1,6 +1,6 @@
from flask import Flask, session, request, render_template
from dotenv import load_dotenv
from .models import Avatars, Users, PostHistory, Posts, MOTD, BadgeUploads
from .models import Avatars, Users, PostHistory, Posts, MOTD, BadgeUploads, Sessions
from .auth import digest
from .routes.users import is_logged_in, get_active_user, get_prefers_theme
from .constants import (
@@ -138,6 +138,16 @@ def bind_default_badges(path):
'uploaded_at': int(os.path.getmtime(real_path)),
})
def clear_stale_sessions():
from .db import db
with db.transaction():
now = int(time.time())
stale_sessions = Sessions.findall([
('expires_at', '<', now)
])
for sess in stale_sessions:
sess.delete()
cache = Cache()
@@ -226,6 +236,8 @@ def create_app():
create_admin()
create_deleted_user()
clear_stale_sessions()
reparse_babycode()
bind_default_badges(app.config['BADGES_PATH'])

View File

@@ -1,5 +1,4 @@
from flask import Blueprint, redirect, url_for, render_template
from app import cache
from datetime import datetime
bp = Blueprint("app", __name__, url_prefix = "/")
@@ -7,12 +6,3 @@ bp = Blueprint("app", __name__, url_prefix = "/")
@bp.route("/")
def index():
return redirect(url_for("topics.all_topics"))
@bp.route("/cache-test")
def cache_test():
test_value = cache.get('test')
if test_value is None:
test_value = 'cached_value_' + str(datetime.now())
cache.set('test', test_value, timeout=10)
return f"set cache: {test_value}"
return f"cached: {test_value}"

View File

@@ -74,7 +74,17 @@ def validate_and_create_badge(input_image, filename):
return False
def is_logged_in():
return "pyrom_session_key" in session
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
def get_active_user():
@@ -83,6 +93,8 @@ def get_active_user():
sess = Sessions.find({"key": session["pyrom_session_key"]})
if not sess:
return None
if sess.expires_at < int(time.time()):
return None
return Users.find({"id": sess.user_id})
@@ -884,6 +896,10 @@ def delete_page_confirm(username):
flash('Incorrect password.', InfoboxKind.ERROR)
return redirect(url_for('.delete_page', username=username))
if target_user.is_admin():
flash('You cannot delete the admin account.', InfoboxKind.ERROR)
return redirect(url_for('.delete_page', username=username))
anonymize_user(target_user.id)
sessions = Sessions.findall({'user_id': int(target_user.id)})
for session_obj in sessions:

View File

@@ -1,11 +1,31 @@
### REQUIRED CONFIGURATION
## the following settings are required.
## the app will not work if they are missing.
# the domain name you will be serving Pyrom from, without the scheme, including the subdomain(s).
# this is overridden by the app in development.
# used for generating URLs.
# the app will not start if this field is missing.
SERVER_NAME = "forum.your.domain"
### OPTIONAL CONFIGURATION
## the following settings are set to their default values.
## you can override any of them.
# your forum's name, shown on the header.
SITE_NAME = "Pyrom"
DISABLE_SIGNUP = false # if true, no one can sign up.
# if true, users can not sign up manually. see the following two settings.
DISABLE_SIGNUP = false
# if neither of the following two options is true,
# no one can sign up. this may be useful later when/if LDAP is implemented.
MODS_CAN_INVITE = true # if true, allows moderators to create invite links. useless unless DISABLE_SIGNUP to be true.
USERS_CAN_INVITE = false # if true, allows users to create invite links. useless unless DISABLE_SIGNUP to be true.
# if true, allows moderators to create invite links. useless unless DISABLE_SIGNUP is true.
MODS_CAN_INVITE = true
# if true, allows users to create invite links. useless unless DISABLE_SIGNUP is true.
USERS_CAN_INVITE = false
# contact information, will be shown in /guides/contact
# some babycodes allowed