132 lines
3.7 KiB
Python
132 lines
3.7 KiB
Python
# retoor <retoor@molodetz.nl>
|
|||
|
|
import re
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||
|
|
|
||
|
|
from molodetz.constants import JOIN_STATUSES, POST_STATUSES, ROLES, TOPICS
|
||
|
|
|
||
|
|
URL_HOST_PATTERN = re.compile(r"^https?://[^\s/]+\.[^\s/]+")
|
||
|
|
PROSE_CAP = 125000
|
||
|
|
|
||
|
|
|
||
|
|
def normalize_website(value):
|
||
|
|
value = (value or "").strip()
|
||
|
|
if not value:
|
||
|
|
return None
|
||
|
|
if not value.lower().startswith(("http://", "https://")):
|
||
|
|
value = "https://" + value
|
||
|
|
if not URL_HOST_PATTERN.match(value):
|
||
|
|
raise ValueError("Geef een geldige link op, bijvoorbeeld https://git.example.nl/naam")
|
||
|
|
return value[:500]
|
||
|
|
|
||
|
|
|
||
|
|
def normalize_european_date(value):
|
||
|
|
value = (value or "").strip()
|
||
|
|
if not value:
|
||
|
|
return None
|
||
|
|
for fmt in ("%d/%m/%Y", "%Y-%m-%d"):
|
||
|
|
try:
|
||
|
|
return datetime.strptime(value, fmt).date().isoformat()
|
||
|
|
except ValueError:
|
||
|
|
continue
|
||
|
|
raise ValueError("Gebruik DD/MM/JJJJ")
|
||
|
|
|
||
|
|
|
||
|
|
class FormModel(BaseModel):
|
||
|
|
model_config = ConfigDict(str_strip_whitespace=True, extra="ignore")
|
||
|
|
|
||
|
|
|
||
|
|
class LoginForm(FormModel):
|
||
|
|
username: str = Field(min_length=3, max_length=255)
|
||
|
|
password: str = Field(min_length=6, max_length=128)
|
||
|
|
remember: bool = False
|
||
|
|
next: str | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class PostForm(FormModel):
|
||
|
|
title: str = Field(min_length=3, max_length=200)
|
||
|
|
body: str = Field(min_length=10, max_length=PROSE_CAP)
|
||
|
|
topic: str = "rol"
|
||
|
|
status: str = "draft"
|
||
|
|
is_placeholder: bool = False
|
||
|
|
|
||
|
|
@field_validator("topic")
|
||
|
|
@classmethod
|
||
|
|
def check_topic(cls, value):
|
||
|
|
if value not in TOPICS:
|
||
|
|
raise ValueError("Onbekend onderwerp")
|
||
|
|
return value
|
||
|
|
|
||
|
|
@field_validator("status")
|
||
|
|
@classmethod
|
||
|
|
def check_status(cls, value):
|
||
|
|
if value not in POST_STATUSES:
|
||
|
|
raise ValueError("Onbekende status")
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
class JoinForm(FormModel):
|
||
|
|
name: str = Field(min_length=2, max_length=80)
|
||
|
|
contact: str = Field(min_length=3, max_length=255)
|
||
|
|
repo_url: str | None = Field(default=None, max_length=500)
|
||
|
|
message: str | None = Field(default=None, max_length=2000)
|
||
|
|
website: str | None = Field(default=None, max_length=200)
|
||
|
|
|
||
|
|
@field_validator("repo_url")
|
||
|
|
@classmethod
|
||
|
|
def check_repo(cls, value):
|
||
|
|
return normalize_website(value)
|
||
|
|
|
||
|
|
|
||
|
|
class JoinStatusForm(FormModel):
|
||
|
|
status: str
|
||
|
|
|
||
|
|
@field_validator("status")
|
||
|
|
@classmethod
|
||
|
|
def check_status(cls, value):
|
||
|
|
if value not in JOIN_STATUSES:
|
||
|
|
raise ValueError("Onbekende status")
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
class RoleForm(FormModel):
|
||
|
|
role: str
|
||
|
|
|
||
|
|
@field_validator("role")
|
||
|
|
@classmethod
|
||
|
|
def check_role(cls, value):
|
||
|
|
if value not in ROLES:
|
||
|
|
raise ValueError("Onbekende rol")
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
class SettingsForm(FormModel):
|
||
|
|
site_title: str | None = Field(default=None, max_length=80)
|
||
|
|
site_tagline: str | None = Field(default=None, max_length=200)
|
||
|
|
maintenance_mode: str | None = None
|
||
|
|
maintenance_message: str | None = Field(default=None, max_length=500)
|
||
|
|
rate_limit_per_minute: str | None = None
|
||
|
|
rate_limit_window_seconds: str | None = None
|
||
|
|
session_max_age_days: str | None = None
|
||
|
|
session_remember_days: str | None = None
|
||
|
|
terms_version: str | None = None
|
||
|
|
friendly_404: str | None = None
|
||
|
|
audit_retention_days: str | None = None
|
||
|
|
outbound_proxy_url: str | None = Field(default=None, max_length=500)
|
||
|
|
|
||
|
|
|
||
|
|
class ServiceConfigForm(FormModel):
|
||
|
|
model_config = ConfigDict(extra="allow", str_strip_whitespace=True)
|
||
|
|
|
||
|
|
|
||
|
|
class BackupForm(FormModel):
|
||
|
|
target: str = "database"
|
||
|
|
|
||
|
|
@field_validator("target")
|
||
|
|
@classmethod
|
||
|
|
def check_target(cls, value):
|
||
|
|
if value not in ("database", "uploads", "keys", "full"):
|
||
|
|
raise ValueError("Onbekend doel")
|
||
|
|
return value
|