Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 KiB
Coding Conventions
Analysis Date: 2026-06-02
Naming Patterns
Python files:
snake_casethroughout —auth.py,cloud_utils.py,document_tasks.py- Modules named for their responsibility, not their layer (e.g.,
services/auth.py,services/audit.py)
Python functions:
snake_casefor all functions and methods:hash_password,verify_password,create_access_token,write_audit_log- Private helpers prefixed with underscore:
_set_refresh_cookie,_port_open,_set_doc_user_id - Async functions use same convention — no
async_prefix
Python classes:
PascalCasefor ORM models and Pydantic models:User,Document,RegisterRequest,DocumentPatch- Request/response models end in
RequestorResponse:RegisterRequest,LoginRequest,ChangePasswordRequest
Python variables:
snake_case:user_id,access_token,used_bytes,credentials_enc- Constants use
UPPER_SNAKE_CASE:_PASSWORD_DETAIL(underscore prefix when module-private) - Module-level singletons prefixed underscore:
_pwd,_CLOUD_PROVIDERS
DB column naming:
snake_casefor all columns:user_id,password_hash,is_active,created_at- Exception: ORM attribute
metadata_maps to DB columnmetadata(reserved SQLAlchemy name) - Timestamp columns use
_atsuffix:created_at,used_at - Boolean columns use
is_or no prefix:is_active,totp_enabled,password_must_change
Frontend files:
- Vue components:
PascalCase—DocumentCard.vue,FolderTreeItem.vue,StorageBrowser.vue - Stores:
camelCase.js—auth.js,documents.js,cloudConnections.js - Utilities:
camelCase.js—formatters.js - API client: single file
src/api/client.js - Test files:
ComponentName.test.jsorstoreName.test.jsinside__tests__/subdirectory
Frontend functions and variables:
camelCase:formatDate,formatSize,providerColor,fetchDocuments,uploadToMinIO- Store composables use
useprefix:useAuthStore,useFoldersStore,useDocumentsStore - Private helpers prefixed underscore:
_refreshInFlight - Event names emitted from components:
kebab-case—'breadcrumb-navigate','folder-create','file-open'
Code Style
Formatting:
- No Prettier, ESLint, Black, or Ruff config committed — style maintained by convention only
- Backend follows PEP 8 organically; 4-space indentation
- Tailwind CSS utility classes applied inline in Vue templates; no scoped
<style>blocks used
Python style specifics:
from __future__ import annotationsat top of allapi/andservices/files (all 8 api/ files confirmed)Optional[X]used instead ofX | Noneunion syntax — maintained for Python < 3.10 compatibility even though runtime is 3.12- Type annotations on all function signatures and ORM
Mapped[...]column declarations - Docstrings present on all public functions and modules; module docstrings explain invariants and phase context
Vue/JS style specifics:
<script setup>Composition API used for ALL Vue components — no Options API exists (all 30+ components confirmed)- Pinia stores use setup function syntax (not options syntax):
defineStore('name', () => { ... }) ref()for all reactive state;computed()for derived values;watch()for side effects- Props always explicitly typed:
{ type: Object, required: true } emitsdeclared on components that emit events
Import Organization
Python imports (consistent order across all api/ and services/ files):
from __future__ import annotations(first line, when present)- Standard library (
import uuid,import hashlib,import logging) - Third-party (
from fastapi import ...,from sqlalchemy import ...,from pydantic import ...) - Internal (
from config import settings,from db.models import ...,from deps.auth import ...,from services import ...)
Example from backend/api/auth.py:
from __future__ import annotations
import uuid
from typing import Literal, Optional
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from pydantic import BaseModel, EmailStr
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from config import settings
from db.models import BackupCode, Quota, RefreshToken, User
from deps.auth import get_current_user
from deps.db import get_db
from services import auth as auth_service
Frontend imports (consistent order):
import { ... } from 'vue'— Vue composablesimport { ... } from 'vue-router'— router composablesimport { useXStore } from '../stores/x.js'— Pinia storesimport * as api from '../../api/client.js'— API client (namespace import)import ChildComponent from './ChildComponent.vue'— child componentsimport { formatDate } from '../../utils/formatters.js'— shared utilities
Path resolution: Relative paths throughout — no @/ alias configured.
Error Handling
Backend — service vs API layer separation (strict pattern):
services/functions raiseValueErrorwith descriptive messages — NEVERHTTPExceptionapi/handlers catchValueErrorand map to HTTP status codes- Pattern from
api/auth.py:try: auth_service.validate_password_strength(body.new_password) except ValueError as exc: raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))
HTTP status codes used:
201— resource created (register, share, folder)401— unauthenticated or wrong credentials403— forbidden (wrong role, wrong owner, admin blocked from document content)404— not found409— conflict (duplicate email/handle)413— quota exceeded422— validation failure (weak password, invalid field value)429— rate limited
Audit log exceptions:
services/audit.pywrite_audit_log()catches all exceptions and callslogger.warning()- Audit failure MUST NOT abort the primary operation — no re-raise under any circumstance
Frontend error handling:
- Stores catch errors and set
error.value = e.message;loading.valuealways reset infinally api/client.jsrequest()throwsErrorwith.statusand optional.payloadproperties- On 401: automatic single-retry after
authStore.refresh(); on refresh failure throws'Session expired'
Logging
Framework: Python logging module with logger = logging.getLogger(__name__) per module.
Patterns:
%-style format strings (never f-strings in log calls):logger.warning("audit log write failed: %s", exc)logger.infofor successful notable operations;logger.warningfor non-fatal failures;logger.errorfor operation failures- Never log secrets, tokens, passwords, or PII
- Auth events, quota violations, and admin actions are written to the
AuditLogDB table viawrite_audit_log()— not the Python logger
Frontend: No logging framework — console.* not used in production code.
Comments
Module docstrings — every backend module has:
- Summary of what it implements (with HTTP endpoint paths)
- Security invariants it enforces (with REQ-IDs:
SEC-02,AUTH-07,D-04) - Plan/phase traceability note
Inline comments:
- Security-sensitive lines carry rationale:
# CLAUDE.md constraint,# SEC-06,# T-03-22 - SQLAlchemy quirks explained inline where non-obvious
# ── Section Name ──────horizontal rules separate logical sections within long files
Test docstrings:
- Every test function has a one-line docstring describing what it asserts:
"""POST /api/auth/register with valid data returns 201 with id and handle."""
Function Design
Backend:
- Single responsibility per function — auth service functions do exactly one thing
- DB-touching functions are
asyncand takeAsyncSessionas a parameter - Pydantic
@field_validatorused for complex field constraints (e.g.,filename_no_path_separators)
Frontend:
- Store actions are
asyncfunctions defined insidedefineStoresetup - Utility functions in
src/utils/formatters.jsare pure — no side effects, no imports - Test factory helpers follow
makeFolder(overrides = {})pattern — spread overrides over defaults
Module Design
Backend:
- All routers named
router:router = APIRouter(prefix="/api/...", tags=[...]) - Settings singleton:
settings = Settings()at bottom ofconfig.py; imported asfrom config import settings - No
__all__declarations — convention limits what callers import
Frontend:
- Named exports from stores:
export const useAuthStore = defineStore(...) - Named exports from utilities:
export function formatDate(iso) { ... } - Default exports from Vue components (implicit via
<script setup>) src/api/client.js: named exports only;request()is unexported internal helper
Backend Dependency Injection
FastAPI Depends() is used for all cross-cutting concerns. Three standard dependencies in backend/deps/:
get_db(deps/db.py) — yieldsAsyncSession; overridden in tests with in-memory SQLite sessionget_current_user(deps/auth.py) — validates Bearer JWT, returnsUser; raises 401get_current_admin(deps/auth.py) — delegates toget_current_user, checksrole == 'admin'; raises 403get_regular_user(deps/auth.py) — delegates toget_current_user, blocksrole == 'admin'; raises 403
Usage pattern in route handlers:
@router.get("/protected")
async def protected_endpoint(
current_user: User = Depends(get_regular_user),
session: AsyncSession = Depends(get_db),
):
...
Security-Enforced Invariants in Code
The following patterns are mandatory and must not be deviated from:
- Token storage:
accessTokenlives only in Piniaref()— neverlocalStorage, neversessionStorage - Refresh cookie:
httponly=True, secure=True, samesite="strict"on everyset_cookiecall - Ownership check: every document/folder/share endpoint asserts
resource.user_id == current_user.id - Object keys:
{user_id}/{document_id}/{uuid4()}{ext}— human filename stored in DB only - Quota: atomic
UPDATE quotas SET used_bytes = used_bytes + $delta WHERE (used_bytes + $delta) <= limit_bytes RETURNING used_bytes— never read-then-write - Admin exclusion: admin accounts blocked from all
/api/documents/*endpoints viaget_regular_user
Convention analysis: 2026-06-02