2026-06-12 01:58:46 +02:00
|
|
|
# retoor <retoor@molodetz.nl>
|
|
|
|
|
|
2026-07-06 05:57:47 +02:00
|
|
|
import re
|
|
|
|
|
|
2026-06-09 00:30:25 +02:00
|
|
|
from datetime import datetime
|
2026-06-09 06:41:27 +02:00
|
|
|
from typing import Literal, Optional
|
2026-07-09 02:52:54 +02:00
|
|
|
from urllib.parse import urlsplit
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
2026-06-06 16:31:42 +02:00
|
|
|
from devplacepy.constants import TOPICS, REACTION_EMOJI
|
2026-06-16 05:32:19 +02:00
|
|
|
from devplacepy.config import DEFAULT_CORRECTION_PROMPT, DEFAULT_MODIFIER_PROMPT
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
|
2026-06-09 00:30:25 +02:00
|
|
|
def normalize_european_date(value):
|
|
|
|
|
if not value:
|
|
|
|
|
return ""
|
|
|
|
|
text = str(value).strip()
|
|
|
|
|
if not text:
|
|
|
|
|
return ""
|
|
|
|
|
for fmt in ("%d/%m/%Y", "%Y-%m-%d"):
|
|
|
|
|
try:
|
|
|
|
|
return datetime.strptime(text, fmt).strftime("%Y-%m-%d")
|
|
|
|
|
except ValueError:
|
|
|
|
|
continue
|
|
|
|
|
raise ValueError("Date must be in DD/MM/YYYY format")
|
|
|
|
|
|
|
|
|
|
|
feat: add api key auth, devii agent, openai gateway, and admin service management
This commit introduces a comprehensive set of new features including API key authentication with CLI management commands (get, reset, backfill), a Devii agentic assistant with WebSocket terminal and session bootstrap, an OpenAI-compatible LLM gateway service, and an admin service management panel. It also adds Playwright browser automation for bot support, configures internal gateway URLs, refactors content editing/deletion to support JSON API responses, and updates documentation across AGENTS.md, README.md, and the developer docs site.
2026-06-08 17:38:33 +02:00
|
|
|
def normalize_poll_options(value):
|
|
|
|
|
if value is None:
|
|
|
|
|
return []
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
value = [value]
|
|
|
|
|
if not isinstance(value, list):
|
|
|
|
|
return value
|
|
|
|
|
if len(value) == 1 and isinstance(value[0], str):
|
|
|
|
|
single = value[0]
|
|
|
|
|
separator = "\n" if "\n" in single else ("," if "," in single else "")
|
|
|
|
|
if separator:
|
|
|
|
|
return [part.strip() for part in single.split(separator) if part.strip()]
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class SignupForm(BaseModel):
|
|
|
|
|
username: str = Field(min_length=3, max_length=32)
|
|
|
|
|
email: str = Field(min_length=1, max_length=255)
|
2026-05-10 09:08:12 +02:00
|
|
|
password: str = Field(min_length=6, max_length=128)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
confirm_password: str = Field(min_length=1, max_length=128)
|
2026-05-10 09:08:12 +02:00
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
@field_validator("username")
|
|
|
|
|
@classmethod
|
|
|
|
|
def username_chars(cls, value):
|
2026-06-09 18:48:08 +02:00
|
|
|
if not value.isascii() or not all(
|
|
|
|
|
c.isalnum() or c in ("-", "_") for c in value
|
|
|
|
|
):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Username can only contain letters, numbers, hyphens, and underscores"
|
|
|
|
|
)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
return value
|
2026-05-10 09:08:12 +02:00
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
@field_validator("email")
|
|
|
|
|
@classmethod
|
|
|
|
|
def email_has_at(cls, value):
|
|
|
|
|
if "@" not in value:
|
|
|
|
|
raise ValueError("Valid email is required")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def passwords_match(self):
|
|
|
|
|
if self.password != self.confirm_password:
|
|
|
|
|
raise ValueError("Passwords do not match")
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginForm(BaseModel):
|
|
|
|
|
email: str = Field(min_length=1, max_length=255)
|
|
|
|
|
password: str = Field(min_length=1, max_length=128)
|
|
|
|
|
remember_me: str = ""
|
2026-06-05 19:02:30 +02:00
|
|
|
next: str = ""
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ForgotPasswordForm(BaseModel):
|
|
|
|
|
email: str = Field(min_length=1, max_length=255)
|
|
|
|
|
|
|
|
|
|
@field_validator("email")
|
|
|
|
|
@classmethod
|
|
|
|
|
def email_has_at(cls, value):
|
|
|
|
|
if "@" not in value:
|
|
|
|
|
raise ValueError("Valid email is required")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResetPasswordForm(BaseModel):
|
2026-05-10 09:08:12 +02:00
|
|
|
password: str = Field(min_length=6, max_length=128)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
confirm_password: str = Field(min_length=1, max_length=128)
|
2026-05-10 09:08:12 +02:00
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def passwords_match(self):
|
|
|
|
|
if self.password != self.confirm_password:
|
|
|
|
|
raise ValueError("Passwords do not match")
|
|
|
|
|
return self
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class PostForm(BaseModel):
|
2026-06-11 00:17:25 +02:00
|
|
|
content: str = Field(min_length=10, max_length=125000)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
title: str = Field(default="", max_length=500)
|
|
|
|
|
topic: str = "random"
|
2026-06-12 06:30:08 +02:00
|
|
|
project_uid: str = Field(default="", max_length=36)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
attachment_uids: list[str] = []
|
2026-06-06 16:31:42 +02:00
|
|
|
poll_question: str = Field(default="", max_length=200)
|
|
|
|
|
poll_options: list[str] = []
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-06-12 06:30:08 +02:00
|
|
|
@field_validator("poll_options")
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll_options_max_length(cls, value):
|
|
|
|
|
if value is None:
|
|
|
|
|
return value
|
|
|
|
|
for opt in value:
|
|
|
|
|
if isinstance(opt, str) and len(opt) > 200:
|
|
|
|
|
raise ValueError("Each poll option must be 200 characters or fewer")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
@field_validator("topic")
|
|
|
|
|
@classmethod
|
|
|
|
|
def valid_topic(cls, value):
|
|
|
|
|
return value if value in TOPICS else "random"
|
|
|
|
|
|
|
|
|
|
@field_validator("poll_options", mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def split_poll_options(cls, value):
|
|
|
|
|
return normalize_poll_options(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PostEditForm(BaseModel):
|
|
|
|
|
content: str = Field(min_length=10, max_length=125000)
|
|
|
|
|
title: str = Field(default="", max_length=500)
|
|
|
|
|
topic: str = "random"
|
|
|
|
|
poll_question: str = Field(default="", max_length=200)
|
|
|
|
|
poll_options: list[str] = []
|
|
|
|
|
|
|
|
|
|
@field_validator("poll_options")
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll_options_max_length(cls, value):
|
|
|
|
|
if value is None:
|
|
|
|
|
return value
|
|
|
|
|
for opt in value:
|
|
|
|
|
if isinstance(opt, str) and len(opt) > 200:
|
|
|
|
|
raise ValueError("Each poll option must be 200 characters or fewer")
|
|
|
|
|
return value
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
@field_validator("topic")
|
|
|
|
|
@classmethod
|
|
|
|
|
def valid_topic(cls, value):
|
|
|
|
|
return value if value in TOPICS else "random"
|
2026-05-10 09:08:12 +02:00
|
|
|
|
feat: add api key auth, devii agent, openai gateway, and admin service management
This commit introduces a comprehensive set of new features including API key authentication with CLI management commands (get, reset, backfill), a Devii agentic assistant with WebSocket terminal and session bootstrap, an OpenAI-compatible LLM gateway service, and an admin service management panel. It also adds Playwright browser automation for bot support, configures internal gateway URLs, refactors content editing/deletion to support JSON API responses, and updates documentation across AGENTS.md, README.md, and the developer docs site.
2026-06-08 17:38:33 +02:00
|
|
|
@field_validator("poll_options", mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def split_poll_options(cls, value):
|
|
|
|
|
return normalize_poll_options(value)
|
|
|
|
|
|
2026-05-10 09:08:12 +02:00
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class CommentForm(BaseModel):
|
2026-07-19 18:57:43 +02:00
|
|
|
content: str = Field(min_length=3, max_length=125000)
|
2026-06-12 06:30:08 +02:00
|
|
|
target_uid: str = Field(default="", max_length=36)
|
|
|
|
|
post_uid: str = Field(default="", max_length=36)
|
2026-06-14 09:48:10 +02:00
|
|
|
target_type: Literal["post", "project", "news", "issue", "gist"] = "post"
|
2026-06-12 06:30:08 +02:00
|
|
|
parent_uid: str = Field(default="", max_length=36)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
attachment_uids: list[str] = []
|
|
|
|
|
|
|
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def require_target(self):
|
|
|
|
|
if not (self.target_uid or self.post_uid):
|
|
|
|
|
raise ValueError("A target is required")
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
feat: enforce hard test-tier requirement across all DevPlace workflow agents and feature-builder docs
Update the feature-builder agent prompt, test-maintainer agent, and all four workflow JS files (devii-tool, endpoint, feature, job-service) to codify the DevPlace test standard as a non-optional project requirement: one test file per endpoint, directory tree mirroring the URL/source path, split into three tiers (unit, api, e2e). Add explicit Test phases to devii-tool, endpoint, feature, and job-service workflows, and embed tier-specific test instructions (path mapping, fixture choice, coverage scope) directly in each workflow's meta description and TESTS constant.
2026-06-15 14:10:14 +02:00
|
|
|
class CommentEditForm(BaseModel):
|
2026-07-19 18:57:43 +02:00
|
|
|
content: str = Field(min_length=3, max_length=125000)
|
feat: enforce hard test-tier requirement across all DevPlace workflow agents and feature-builder docs
Update the feature-builder agent prompt, test-maintainer agent, and all four workflow JS files (devii-tool, endpoint, feature, job-service) to codify the DevPlace test standard as a non-optional project requirement: one test file per endpoint, directory tree mirroring the URL/source path, split into three tiers (unit, api, e2e). Add explicit Test phases to devii-tool, endpoint, feature, and job-service workflows, and embed tier-specific test instructions (path mapping, fixture choice, coverage scope) directly in each workflow's meta description and TESTS constant.
2026-06-15 14:10:14 +02:00
|
|
|
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class ProjectForm(BaseModel):
|
2026-05-10 09:08:12 +02:00
|
|
|
title: str = Field(min_length=1, max_length=200)
|
|
|
|
|
description: str = Field(min_length=1, max_length=5000)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
release_date: str = ""
|
|
|
|
|
demo_date: str = ""
|
2026-06-09 18:48:08 +02:00
|
|
|
project_type: Literal["game", "game_asset", "software", "mobile_app", "website"] = (
|
|
|
|
|
"software"
|
|
|
|
|
)
|
2026-05-10 09:08:12 +02:00
|
|
|
platforms: str = Field(default="", max_length=500)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
status: str = Field(default="In Development", max_length=100)
|
2026-06-09 06:41:27 +02:00
|
|
|
is_private: bool = False
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
attachment_uids: list[str] = []
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-06-09 00:30:25 +02:00
|
|
|
@field_validator("release_date", "demo_date", mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def normalize_dates(cls, value):
|
|
|
|
|
return normalize_european_date(value)
|
|
|
|
|
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-06-11 00:17:25 +02:00
|
|
|
class ProjectEditForm(BaseModel):
|
|
|
|
|
title: str = Field(min_length=1, max_length=200)
|
|
|
|
|
description: str = Field(min_length=1, max_length=5000)
|
|
|
|
|
release_date: str = ""
|
|
|
|
|
demo_date: str = ""
|
|
|
|
|
project_type: Literal["game", "game_asset", "software", "mobile_app", "website"] = (
|
|
|
|
|
"software"
|
|
|
|
|
)
|
|
|
|
|
platforms: str = Field(default="", max_length=500)
|
|
|
|
|
status: str = Field(default="In Development", max_length=100)
|
|
|
|
|
|
|
|
|
|
@field_validator("release_date", "demo_date", mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def normalize_dates(cls, value):
|
|
|
|
|
return normalize_european_date(value)
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 05:32:19 +02:00
|
|
|
class BackupRunForm(BaseModel):
|
|
|
|
|
target: Literal["database", "uploads", "keys", "full"] = "full"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BackupScheduleForm(BaseModel):
|
|
|
|
|
name: str = Field(min_length=1, max_length=120)
|
|
|
|
|
target: Literal["database", "uploads", "keys", "full"] = "full"
|
|
|
|
|
kind: Literal["interval", "cron"] = "interval"
|
|
|
|
|
every_seconds: int = Field(default=86400, ge=60)
|
|
|
|
|
cron: str = Field(default="", max_length=120)
|
|
|
|
|
keep_last: int = Field(default=7, ge=0, le=1000)
|
|
|
|
|
|
|
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def _validate_schedule(self) -> "BackupScheduleForm":
|
|
|
|
|
if self.kind == "cron" and not self.cron.strip():
|
|
|
|
|
raise ValueError("A cron schedule requires a cron expression")
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
2026-06-09 06:41:27 +02:00
|
|
|
class ProjectFlagForm(BaseModel):
|
|
|
|
|
value: bool = False
|
|
|
|
|
|
|
|
|
|
|
2026-06-09 22:52:51 +02:00
|
|
|
class CustomizationToggleForm(BaseModel):
|
|
|
|
|
value: bool = False
|
|
|
|
|
|
|
|
|
|
|
2026-07-09 02:52:54 +02:00
|
|
|
class AwardGiveForm(BaseModel):
|
|
|
|
|
description: str = Field(min_length=1, max_length=125)
|
|
|
|
|
|
|
|
|
|
|
2026-06-13 12:09:48 +02:00
|
|
|
class NotificationPrefForm(BaseModel):
|
|
|
|
|
notification_type: str = Field(min_length=1, max_length=40)
|
feat: add telegram notification channel with outbox service and per-user preferences
Extend the notification system with a third channel (telegram) alongside existing in_app and push channels. Add `telegram_enabled` column to `notification_preferences` table, update `NOTIFICATION_CHANNELS` and `_NOTIFICATION_CHANNEL_COLUMNS` mappings, and set telegram default to off (`_NOTIFICATION_CHANNEL_DEFAULTS`). Create `telegram_outbox` table with columns for uid, user_uid, chat_id, text, status, attempts, created_at, and sent_at, plus an index on status/id for efficient polling. Register `TelegramOutboxService` in the service manager lifecycle. Update API documentation strings to describe the new channel and its pairing requirement. Extend admin notification defaults view to include telegram column. Pass `notif_telegram_paired` flag to profile template based on `telegram_store.is_paired()` check. Update `NotificationPrefForm` and `NotificationDefaultForm` model literals to accept "telegram" as a valid channel value.
2026-06-22 22:52:02 +02:00
|
|
|
channel: Literal["in_app", "push", "telegram"]
|
2026-06-13 12:09:48 +02:00
|
|
|
value: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NotificationDefaultForm(BaseModel):
|
|
|
|
|
notification_type: str = Field(min_length=1, max_length=40)
|
feat: add telegram notification channel with outbox service and per-user preferences
Extend the notification system with a third channel (telegram) alongside existing in_app and push channels. Add `telegram_enabled` column to `notification_preferences` table, update `NOTIFICATION_CHANNELS` and `_NOTIFICATION_CHANNEL_COLUMNS` mappings, and set telegram default to off (`_NOTIFICATION_CHANNEL_DEFAULTS`). Create `telegram_outbox` table with columns for uid, user_uid, chat_id, text, status, attempts, created_at, and sent_at, plus an index on status/id for efficient polling. Register `TelegramOutboxService` in the service manager lifecycle. Update API documentation strings to describe the new channel and its pairing requirement. Extend admin notification defaults view to include telegram column. Pass `notif_telegram_paired` flag to profile template based on `telegram_store.is_paired()` check. Update `NotificationPrefForm` and `NotificationDefaultForm` model literals to accept "telegram" as a valid channel value.
2026-06-22 22:52:02 +02:00
|
|
|
channel: Literal["in_app", "push", "telegram"]
|
2026-06-13 12:09:48 +02:00
|
|
|
value: bool = False
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 05:32:19 +02:00
|
|
|
class AiCorrectionForm(BaseModel):
|
|
|
|
|
enabled: bool = False
|
|
|
|
|
sync: bool = False
|
2026-07-19 18:57:43 +02:00
|
|
|
prompt: str = Field(default=DEFAULT_CORRECTION_PROMPT, max_length=20000)
|
2026-06-16 05:32:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class AiModifierForm(BaseModel):
|
|
|
|
|
enabled: bool = False
|
|
|
|
|
sync: bool = False
|
2026-07-19 18:57:43 +02:00
|
|
|
prompt: str = Field(default=DEFAULT_MODIFIER_PROMPT, max_length=20000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InteractionsForm(BaseModel):
|
|
|
|
|
enabled: bool = True
|
|
|
|
|
reset: bool = False
|
2026-06-16 05:32:19 +02:00
|
|
|
|
|
|
|
|
|
2026-06-19 00:09:34 +02:00
|
|
|
class TelegramPairForm(BaseModel):
|
|
|
|
|
action: str = Field(default="request", max_length=16)
|
|
|
|
|
|
|
|
|
|
|
2026-06-09 16:06:02 +02:00
|
|
|
class ForkForm(BaseModel):
|
|
|
|
|
title: str = Field(min_length=1, max_length=200)
|
|
|
|
|
|
|
|
|
|
|
2026-06-19 14:25:51 +02:00
|
|
|
class PlanningForm(BaseModel):
|
|
|
|
|
numbers: str = Field(default="", max_length=4096)
|
|
|
|
|
|
|
|
|
|
|
2026-06-11 00:17:25 +02:00
|
|
|
class UploadUrlForm(BaseModel):
|
|
|
|
|
url: str = Field(min_length=1, max_length=2048)
|
|
|
|
|
filename: Optional[str] = Field(default=None, max_length=255)
|
|
|
|
|
|
|
|
|
|
|
feat: add project file system with CRUD, upload, inline editing, and video attachment support
- Add new `/projects/{slug}/files` endpoint group for per-project filesystem operations including directory and file CRUD, upload, and inline editing with public read and owner write access
- Extend attachment system to support video formats (webm, ogv, mov, m4v) with proper file icons and MIME types
- Implement configurable allowed file types via `allowed_file_types` site setting, replacing hardcoded `ALLOWED_UPLOAD_TYPES` with dynamic `allowed_extensions()` and `is_extension_allowed()` functions
- Add `delete_all_project_files()` call in `delete_content_item()` to clean up project files when a project is deleted
- Create database indexes on `project_files` table for `(project_uid, path)` and `(project_uid, parent_path)` to optimize file lookups
- Introduce `docs_prose.py` module with `render_prose()` function that renders Markdown content inside `data-render` divs using mistune, enabling dynamic prose rendering in documentation pages
- Enhance docs search with Markdown-aware text stripping (`_demarkdown()`) and improved HTML/script/style sanitization for better search indexing
- Update documentation API samples to reflect new attachment response fields (`is_image`, `is_video`, `mime_type`) and note video format support
- Update README to document the new project files endpoint and clarify AI gateway attribution for guest Devii sessions
2026-06-08 22:51:09 +02:00
|
|
|
class ProjectFileWriteForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
content: str = Field(default="", max_length=400000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectFileMkdirForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectFileMoveForm(BaseModel):
|
|
|
|
|
from_path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
to_path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectFileDeleteForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
|
|
|
|
|
|
2026-06-09 06:41:27 +02:00
|
|
|
class ProjectFileReplaceLinesForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
start: int = Field(ge=1)
|
|
|
|
|
end: int = Field(ge=0)
|
|
|
|
|
content: str = Field(default="", max_length=400000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectFileInsertLinesForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
at: int = Field(ge=1)
|
|
|
|
|
content: str = Field(default="", max_length=400000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectFileDeleteLinesForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
start: int = Field(ge=1)
|
|
|
|
|
end: int = Field(ge=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectFileAppendForm(BaseModel):
|
|
|
|
|
path: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
content: str = Field(default="", max_length=400000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContainerInstanceForm(BaseModel):
|
|
|
|
|
name: str = Field(min_length=1, max_length=64)
|
|
|
|
|
boot_command: str = Field(default="", max_length=500)
|
2026-06-14 16:46:36 +02:00
|
|
|
boot_language: str = Field(default="none", max_length=10)
|
|
|
|
|
boot_script: str = Field(default="", max_length=100000)
|
|
|
|
|
run_as_uid: str = Field(default="", max_length=36)
|
|
|
|
|
start_on_boot: bool = False
|
2026-06-09 06:41:27 +02:00
|
|
|
env: str = Field(default="", max_length=10000)
|
|
|
|
|
cpu_limit: str = Field(default="", max_length=16)
|
|
|
|
|
mem_limit: str = Field(default="", max_length=16)
|
|
|
|
|
ports: str = Field(default="", max_length=500)
|
|
|
|
|
volumes: str = Field(default="", max_length=2000)
|
|
|
|
|
restart_policy: str = Field(default="never", max_length=20)
|
|
|
|
|
autostart: bool = True
|
|
|
|
|
ingress_slug: str = Field(default="", max_length=64)
|
|
|
|
|
ingress_port: Optional[int] = Field(default=None, ge=1, le=65535)
|
|
|
|
|
|
2026-06-14 16:46:36 +02:00
|
|
|
@field_validator("ingress_port", mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def _blank_ingress_port(cls, value):
|
|
|
|
|
if value is None or (isinstance(value, str) and not value.strip()):
|
|
|
|
|
return None
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContainerAdminCreateForm(ContainerInstanceForm):
|
|
|
|
|
project_slug: str = Field(min_length=1, max_length=128)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContainerEditForm(BaseModel):
|
|
|
|
|
run_as_uid: str = Field(default="", max_length=36)
|
|
|
|
|
boot_language: str = Field(default="none", max_length=10)
|
|
|
|
|
boot_script: str = Field(default="", max_length=100000)
|
|
|
|
|
boot_command: str = Field(default="", max_length=500)
|
|
|
|
|
restart_policy: str = Field(default="never", max_length=20)
|
|
|
|
|
start_on_boot: bool = False
|
|
|
|
|
cpu_limit: str = Field(default="", max_length=16)
|
|
|
|
|
mem_limit: str = Field(default="", max_length=16)
|
|
|
|
|
|
2026-06-09 06:41:27 +02:00
|
|
|
|
|
|
|
|
class ContainerExecForm(BaseModel):
|
|
|
|
|
command: str = Field(min_length=1, max_length=2000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContainerScheduleForm(BaseModel):
|
|
|
|
|
action: str = Field(max_length=10)
|
|
|
|
|
kind: str = Field(max_length=10)
|
|
|
|
|
cron: str = Field(default="", max_length=120)
|
|
|
|
|
run_at: str = Field(default="", max_length=40)
|
|
|
|
|
delay_seconds: Optional[int] = Field(default=None, ge=1)
|
|
|
|
|
every_seconds: Optional[int] = Field(default=None, ge=1)
|
|
|
|
|
max_runs: Optional[int] = Field(default=None, ge=1)
|
|
|
|
|
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class MessageForm(BaseModel):
|
2026-05-10 09:08:12 +02:00
|
|
|
content: str = Field(min_length=1, max_length=2000)
|
2026-06-12 06:30:08 +02:00
|
|
|
receiver_uid: str = Field(min_length=1, max_length=36)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
attachment_uids: list[str] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProfileForm(BaseModel):
|
|
|
|
|
bio: str = Field(default="", max_length=500)
|
|
|
|
|
location: str = Field(default="", max_length=200)
|
|
|
|
|
git_link: str = Field(default="", max_length=500)
|
|
|
|
|
website: str = Field(default="", max_length=500)
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class GistForm(BaseModel):
|
|
|
|
|
title: str = Field(min_length=1, max_length=200)
|
|
|
|
|
description: str = Field(default="", max_length=5000)
|
feat: add api key auth, devii agent, openai gateway, and admin service management
This commit introduces a comprehensive set of new features including API key authentication with CLI management commands (get, reset, backfill), a Devii agentic assistant with WebSocket terminal and session bootstrap, an OpenAI-compatible LLM gateway service, and an admin service management panel. It also adds Playwright browser automation for bot support, configures internal gateway URLs, refactors content editing/deletion to support JSON API responses, and updates documentation across AGENTS.md, README.md, and the developer docs site.
2026-06-08 17:38:33 +02:00
|
|
|
source_code: str = Field(min_length=1, max_length=400000)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
language: str = Field(default="plaintext", max_length=50)
|
|
|
|
|
attachment_uids: list[str] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GistEditForm(BaseModel):
|
|
|
|
|
title: str = Field(min_length=1, max_length=200)
|
|
|
|
|
description: str = Field(default="", max_length=5000)
|
feat: add api key auth, devii agent, openai gateway, and admin service management
This commit introduces a comprehensive set of new features including API key authentication with CLI management commands (get, reset, backfill), a Devii agentic assistant with WebSocket terminal and session bootstrap, an OpenAI-compatible LLM gateway service, and an admin service management panel. It also adds Playwright browser automation for bot support, configures internal gateway URLs, refactors content editing/deletion to support JSON API responses, and updates documentation across AGENTS.md, README.md, and the developer docs site.
2026-06-08 17:38:33 +02:00
|
|
|
source_code: str = Field(min_length=1, max_length=400000)
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
language: str = Field(default="plaintext", max_length=50)
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 09:48:10 +02:00
|
|
|
class IssueForm(BaseModel):
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
title: str = Field(min_length=1, max_length=200)
|
|
|
|
|
description: str = Field(min_length=1, max_length=5000)
|
2026-06-19 13:22:05 +02:00
|
|
|
attachment_uids: list[str] = []
|
2026-06-12 20:31:40 +02:00
|
|
|
|
|
|
|
|
|
2026-06-14 09:48:10 +02:00
|
|
|
class IssueCommentForm(BaseModel):
|
2026-06-12 20:31:40 +02:00
|
|
|
body: str = Field(min_length=1, max_length=5000)
|
2026-06-19 13:22:05 +02:00
|
|
|
attachment_uids: list[str] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IssueAttachmentForm(BaseModel):
|
|
|
|
|
attachment_uids: list[str] = []
|
2026-06-12 20:31:40 +02:00
|
|
|
|
|
|
|
|
|
2026-06-14 09:48:10 +02:00
|
|
|
class IssueStatusForm(BaseModel):
|
2026-06-12 20:31:40 +02:00
|
|
|
status: Literal["open", "closed"]
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class VoteForm(BaseModel):
|
|
|
|
|
value: int
|
|
|
|
|
|
|
|
|
|
@field_validator("value")
|
|
|
|
|
@classmethod
|
|
|
|
|
def valid_value(cls, value):
|
|
|
|
|
if value not in (1, -1):
|
|
|
|
|
raise ValueError("value must be 1 or -1")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
2026-06-06 16:31:42 +02:00
|
|
|
class ReactionForm(BaseModel):
|
|
|
|
|
emoji: str = Field(min_length=1, max_length=16)
|
|
|
|
|
|
|
|
|
|
@field_validator("emoji")
|
|
|
|
|
@classmethod
|
|
|
|
|
def valid_emoji(cls, value):
|
|
|
|
|
if value not in REACTION_EMOJI:
|
|
|
|
|
raise ValueError("Invalid reaction")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PollVoteForm(BaseModel):
|
2026-06-12 06:30:08 +02:00
|
|
|
option_uid: str = Field(min_length=1, max_length=36)
|
2026-06-06 16:31:42 +02:00
|
|
|
|
|
|
|
|
|
2026-06-14 02:16:22 +02:00
|
|
|
class SeoRunForm(BaseModel):
|
|
|
|
|
url: str = Field(min_length=3, max_length=2000)
|
|
|
|
|
mode: Literal["url", "sitemap"] = "url"
|
|
|
|
|
max_pages: int = Field(default=10, ge=1, le=50)
|
|
|
|
|
|
|
|
|
|
@field_validator("url")
|
|
|
|
|
@classmethod
|
|
|
|
|
def url_scheme(cls, value):
|
|
|
|
|
text = value.strip()
|
|
|
|
|
if not text:
|
|
|
|
|
raise ValueError("A URL is required")
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
2026-07-06 05:57:47 +02:00
|
|
|
ISSLOP_URL_PATTERN = re.compile(r"^(https?://|git://|ssh://|git@)[\w./:@~^-]+$", re.IGNORECASE)
|
|
|
|
|
ISSLOP_SINGLE_SLASH_PATTERN = re.compile(r"^(https?|git|ssh):/(?!/)", re.IGNORECASE)
|
|
|
|
|
ISSLOP_SCHEME_PATTERN = re.compile(r"^[a-z][a-z0-9+.-]*://", re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IsslopRunForm(BaseModel):
|
|
|
|
|
url: str = Field(min_length=4, max_length=2048)
|
|
|
|
|
|
|
|
|
|
@field_validator("url")
|
|
|
|
|
@classmethod
|
|
|
|
|
def url_scheme(cls, value):
|
|
|
|
|
text = value.strip()
|
|
|
|
|
text = ISSLOP_SINGLE_SLASH_PATTERN.sub(lambda match: f"{match.group(1)}://", text)
|
|
|
|
|
if not ISSLOP_SCHEME_PATTERN.match(text) and not text.startswith("git@"):
|
|
|
|
|
text = f"https://{text}"
|
|
|
|
|
if not ISSLOP_URL_PATTERN.match(text):
|
|
|
|
|
raise ValueError("URL must be an http(s), git or ssh source location")
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 03:34:21 +02:00
|
|
|
DEEPSEARCH_MIN_DEPTH = 1
|
|
|
|
|
DEEPSEARCH_MAX_DEPTH = 4
|
|
|
|
|
DEEPSEARCH_DEFAULT_DEPTH = 2
|
|
|
|
|
DEEPSEARCH_MIN_PAGES = 1
|
|
|
|
|
DEEPSEARCH_MAX_PAGES = 30
|
|
|
|
|
DEEPSEARCH_DEFAULT_PAGES = 12
|
|
|
|
|
DEEPSEARCH_MIN_QUERY = 3
|
|
|
|
|
DEEPSEARCH_MAX_QUERY = 500
|
|
|
|
|
DEEPSEARCH_MAX_MESSAGE = 2000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DeepsearchRunForm(BaseModel):
|
|
|
|
|
query: str = Field(min_length=DEEPSEARCH_MIN_QUERY, max_length=DEEPSEARCH_MAX_QUERY)
|
|
|
|
|
depth: int = Field(
|
|
|
|
|
default=DEEPSEARCH_DEFAULT_DEPTH,
|
|
|
|
|
ge=DEEPSEARCH_MIN_DEPTH,
|
|
|
|
|
le=DEEPSEARCH_MAX_DEPTH,
|
|
|
|
|
)
|
|
|
|
|
max_pages: int = Field(
|
|
|
|
|
default=DEEPSEARCH_DEFAULT_PAGES,
|
|
|
|
|
ge=DEEPSEARCH_MIN_PAGES,
|
|
|
|
|
le=DEEPSEARCH_MAX_PAGES,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@field_validator("query")
|
|
|
|
|
@classmethod
|
|
|
|
|
def query_present(cls, value):
|
|
|
|
|
text = value.strip()
|
|
|
|
|
if not text:
|
|
|
|
|
raise ValueError("A research question is required")
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DeepsearchChatForm(BaseModel):
|
|
|
|
|
message: str = Field(min_length=1, max_length=DEEPSEARCH_MAX_MESSAGE)
|
|
|
|
|
|
|
|
|
|
@field_validator("message")
|
|
|
|
|
@classmethod
|
|
|
|
|
def message_present(cls, value):
|
|
|
|
|
text = value.strip()
|
|
|
|
|
if not text:
|
|
|
|
|
raise ValueError("A message is required")
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
feat: replace raw form parsing with pydantic models for auth, admin, bugs, comments, and gists
Migrate all route handlers from manual `request.form()` parsing to typed `Annotated[Model, Form()]` dependencies using new pydantic models (SignupForm, LoginForm, ForgotPasswordForm, AdminRoleForm, AdminPasswordForm, BugForm, CommentForm, GistForm). Remove inline validation logic from signup, login, password reset, admin role/password change, bug creation, comment creation, and gist creation endpoints, delegating validation to model validators and field constraints. Add `RequestValidationError` exception handler with friendly error messages for auth form pages. Update model definitions to use `field_validator` and `model_validator` for cross-field checks like password matching.
2026-05-23 05:44:04 +02:00
|
|
|
class AdminRoleForm(BaseModel):
|
|
|
|
|
role: Literal["member", "admin"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminPasswordForm(BaseModel):
|
|
|
|
|
password: str = Field(min_length=6, max_length=128)
|
2026-05-23 05:55:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminSettingsForm(BaseModel):
|
|
|
|
|
site_name: str = Field(default="", max_length=200)
|
|
|
|
|
site_description: str = Field(default="", max_length=500)
|
|
|
|
|
site_tagline: str = Field(default="", max_length=500)
|
2026-06-09 06:41:27 +02:00
|
|
|
site_url: str = Field(default="", max_length=300)
|
2026-05-23 05:55:50 +02:00
|
|
|
max_upload_size_mb: str = Field(default="", max_length=10)
|
|
|
|
|
allowed_file_types: str = Field(default="", max_length=1000)
|
|
|
|
|
max_attachments_per_resource: str = Field(default="", max_length=10)
|
2026-06-06 16:31:42 +02:00
|
|
|
rate_limit_per_minute: str = Field(default="", max_length=10)
|
|
|
|
|
rate_limit_window_seconds: str = Field(default="", max_length=10)
|
|
|
|
|
session_max_age_days: str = Field(default="", max_length=10)
|
|
|
|
|
session_remember_days: str = Field(default="", max_length=10)
|
|
|
|
|
registration_open: str = Field(default="", max_length=1)
|
|
|
|
|
maintenance_mode: str = Field(default="", max_length=1)
|
|
|
|
|
maintenance_message: str = Field(default="", max_length=300)
|
2026-06-13 23:37:13 +02:00
|
|
|
docs_search_mode: str = Field(default="", max_length=20)
|
2026-07-09 02:52:54 +02:00
|
|
|
outbound_proxy_url: str = Field(default="", max_length=500)
|
2026-06-19 22:15:22 +02:00
|
|
|
extra_head: str = Field(default="", max_length=50000)
|
2026-06-22 18:41:53 +02:00
|
|
|
|
2026-07-09 02:52:54 +02:00
|
|
|
@field_validator("outbound_proxy_url")
|
|
|
|
|
@classmethod
|
|
|
|
|
def validate_outbound_proxy_url(cls, value):
|
|
|
|
|
text = value.strip()
|
|
|
|
|
if not text:
|
|
|
|
|
return text
|
|
|
|
|
parsed = urlsplit(text)
|
|
|
|
|
if parsed.scheme not in ("http", "https", "socks5", "socks5h") or not parsed.hostname:
|
|
|
|
|
raise ValueError("Proxy URL must be http(s):// or socks5(h):// with a host, e.g. http://user:pass@host:port")
|
|
|
|
|
return text
|
|
|
|
|
|
2026-06-22 18:41:53 +02:00
|
|
|
|
|
|
|
|
class GamePlantForm(BaseModel):
|
|
|
|
|
slot: int = Field(ge=0, le=64)
|
|
|
|
|
crop: str = Field(min_length=1, max_length=40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameSlotForm(BaseModel):
|
|
|
|
|
slot: int = Field(ge=0, le=64)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GamePerkForm(BaseModel):
|
|
|
|
|
perk: str = Field(min_length=1, max_length=40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameQuestForm(BaseModel):
|
|
|
|
|
quest: str = Field(min_length=1, max_length=40)
|
2026-07-21 03:36:29 +02:00
|
|
|
scope: str = Field(default="daily", min_length=1, max_length=10)
|
feat: add per-user avatar seed regeneration with irreversible random avatar replacement
Implement a new `avatar_seed` column on the users table that overrides the username-based seed for Multiavatar generation. Introduce a null-safe `avatar_seed(user)` choke point in `avatar.py` that resolves `user.get("avatar_seed") or user.get("username")`, registered as a Jinja global so every render site (`_avatar_link.html`, `avatar_url(...)` calls, SEO `og_image`, issues ad-hoc dicts, devRant payload/PNG) propagates a regenerated seed. Add `POST /profile/{username}/regenerate-avatar` endpoint (owner-or-admin only) that writes a fresh `generate_uid()` to `avatar_seed`, invalidates the target's user cache, and audits `profile.avatar.regenerate`. The previous seed is overwritten and never stored, making regeneration irreversible. Document the feature in `AGENTS.md` and `README.md`, add the API endpoint to `docs_api.py`, and include the `regenerate_avatar` Devii tool in `CONFIRM_REQUIRED`.
2026-06-28 00:31:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameLegacyForm(BaseModel):
|
|
|
|
|
key: str = Field(min_length=1, max_length=40)
|
2026-07-21 03:36:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameInfraForm(BaseModel):
|
|
|
|
|
key: str = Field(min_length=1, max_length=40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameCosmeticForm(BaseModel):
|
|
|
|
|
key: str = Field(min_length=1, max_length=40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameMasteryForm(BaseModel):
|
|
|
|
|
key: str = Field(min_length=1, max_length=40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GameEraStartForm(BaseModel):
|
|
|
|
|
name: str = Field(min_length=1, max_length=60)
|
|
|
|
|
duration_days: int = Field(default=28, ge=1, le=180)
|