{% raw %} # Conventions and rules The project-wide conventions that keep the codebase consistent. They are not style preferences; following them is how new code reads like the existing code. See also [Architecture overview](/docs/architecture.html) and [Development workflow](/docs/architecture-workflow.html). > Audience: administrators and maintainers. These pages are hidden from members and guests, in the sidebar, the search index, and the documentation export. ## Code style - **No comments and no docstrings in source.** Code is self-documenting through clear names. - **Explicit over implicit; simple over complex.** Prefer flat structures and one obvious implementation. Avoid special cases that break an established pattern. - **Never silence errors** unless suppression is the explicit intent. - **No em-dash** anywhere, neither the long dash character nor its HTML entity. Use a hyphen. ## Naming - Names are concise and consistent across the codebase (`user_uid`, `target_type`, not `u_id` or `t`). - **Forbidden suffix and prefix patterns:** `_new`, `_old`, `_temp`, `_v2`, `better_`, `my_`, `the_`. Refactors do not append qualifiers to a name; they replace it. ## Do not repeat queries (DRY) Never write an N+1 loop in a router. Use the batch helpers in `database.py` for listing and feed routes: | Helper | Returns | |--------|---------| | `get_users_by_uids` | Users keyed by uid. | | `get_comment_counts_by_post_uids` | Comment counts per post. | | `get_vote_counts` | Up and down vote tallies per target. | | `load_comments` | A comment tree with votes and reactions for one resource. | | `get_attachments_by_type` | Attachments grouped by target. | Add new query and batch helpers to `database.py` rather than inlining SQL in routers. ## Resource conventions - **Dates are DD/MM/YYYY (European).** Use the `format_date()` template global, or `time_ago()` which switches to DD/MM/YYYY past 30 days. - **Slug + UUID lookup.** Resources with slugs (posts, gists, news, projects) accept either the slug or the bare UUID. Slugs are generated by `make_combined_slug(title, uid)`, which prefixes the last 12 hex characters of the UUID (its random tail, since a uuid7's leading bytes are a millisecond timestamp and would collide for same-title rows written in the same window); resolution is `resolve_by_slug()`. - **Ownership checks and cascade deletes.** Compare `resource["user_uid"] == user["uid"]` (via `content.is_owner`) before edit or delete. Edits stay owner-only; deletes additionally allow any admin (`is_owner(...) or is_admin(user)`). Every removal is a **soft delete**: it stamps `deleted_at`/`deleted_by` instead of dropping the row, and cascades comments, then votes and engagement, then the resource under one shared stamp so the event restores or purges atomically. Only garbage collection is a hard delete. - **Username** is 3 to 32 characters of letters, numbers, hyphens, and underscores. **Password** is a minimum of 6 characters, and that is the only rule: no forced complexity, mandatory symbols, or higher length minimums, which only push people toward weaker, reused, or written-down passwords. Security against online guessing is handled by the per-IP rate limit on the request pipeline, not by complexity rules at the keyboard; brute force over a rate-limited login is not a credible threat model. - **Avatars** are Multiavatar SVGs generated locally from the username seed, in-memory cached, served from `/avatar/multiavatar/{seed}`. ## Polymorphic comments and votes The `comments` table keys on `(target_type, target_uid)`, so the same comment section component serves posts, projects, gists, and news. Votes follow the same shape via `/votes/{target_type}/{uid}`. This is a deliberate single pattern rather than a per-type implementation. ## Validation and redirects - **Form validation is Pydantic-native.** Declare a typed body param `data: Annotated[SomeForm, Form()]` (models live in `models.py`); the global `RequestValidationError` handler re-renders auth pages with messages or redirects other routes. - **`RedirectResponse(url=..., status_code=302)`** always passes `status_code` as an integer literal, never `status.HTTP_302_FOUND`. ## SEO `seo.py` builds JSON-LD schemas (WebSite, BreadcrumbList, DiscussionForumPosting, ProfilePage, SoftwareApplication, NewsArticle, SoftwareSourceCode). Every router builds context with `base_seo_context(request, ...)` and merges it into the response; `base.html` reads `page_title`, `meta_description`, `meta_robots`, `canonical_url`, the `og_*` keys, `breadcrumbs`, and `page_schema`. Auth, messages, and notifications pages are `noindex,nofollow`; a private project's detail page is `noindex,nofollow`; thin profiles are `noindex,follow`. JSON-LD emitted via `{{ page_schema | safe }}` is escaped in `_json_ld_dumps` (`<`, `>`, `&`, and line separators to `\uXXXX`) to prevent a `` breakout. `/robots.txt` and `/sitemap.xml` are served by `routers/seo.py`. {% endraw %}