Folder Structure
cms-backend/
├── app/
│ ├── main.py # FastAPI app, CORS, middleware, router registration
│ ├── api/
│ │ └── router.py # Aggregates all feature routers
│ ├── core/
│ │ ├── config.py # Pydantic settings, .env parsing
│ │ ├── database.py # Engine, SessionLocal, get_db()
│ │ ├── security.py # Password hashing, JWT encode/decode
│ │ ├── dependencies.py # get_current_user, require_permission
│ │ ├── exceptions.py # Custom HTTP exceptions
│ │ ├── pagination.py # paginate(), page_to_skip()
│ │ ├── permissions.py # Permission constants
│ │ ├── logging.py # Structured logging setup
│ │ ├── seed.py # Main seed entry point
│ │ ├── seed_users.py # User/role seed data
│ │ └── seed_translations.py # Translation seed data
│ ├── middleware/
│ │ └── logging_middleware.py # Request/response logging
│ ├── features/
│ │ ├── shared/ # Shared base classes
│ │ │ ├── base_model.py # Base, UUIDMixin, TimestampMixin, SoftDeleteMixin
│ │ │ ├── base_repository.py # BaseRepository with common CRUD
│ │ │ ├── base_schema.py # BaseSchema, UUIDSchema, TimestampSchema
│ │ │ └── pagination.py # PaginationMeta, PaginatedResponse
│ │ │
│ │ ├── auth/ # Authentication & authorization domain
│ │ │ ├── router.py # /auth endpoints
│ │ │ ├── service.py # register, login, refresh, logout
│ │ │ ├── schemas.py # LoginRequest, TokenResponse, LoginResponse
│ │ │ ├── users/
│ │ │ │ ├── model.py # User model + user_roles pivot table
│ │ │ │ ├── repository.py
│ │ │ │ ├── schema.py # UserCreate, UserUpdate, UserResponse...
│ │ │ │ ├── service.py # CRUD, password change, role assignment
│ │ │ │ └── router.py # /users endpoints
│ │ │ ├── roles/
│ │ │ │ ├── model.py # Role model + role_permissions pivot table
│ │ │ │ ├── repository.py
│ │ │ │ ├── schema.py
│ │ │ │ ├── service.py
│ │ │ │ └── router.py # /roles endpoints
│ │ │ ├── permissions/
│ │ │ │ ├── model.py
│ │ │ │ ├── repository.py
│ │ │ │ ├── schema.py
│ │ │ │ ├── service.py
│ │ │ │ └── router.py # /permissions endpoints
│ │ │ └── tokens/
│ │ │ ├── model.py # RefreshToken model
│ │ │ └── repository.py
│ │ │
│ │ ├── translations/ # Translation domain
│ │ │ ├── router.py # /translations public endpoints
│ │ │ ├── languages/
│ │ │ │ ├── model.py
│ │ │ │ ├── repository.py
│ │ │ │ ├── schema.py
│ │ │ │ ├── service.py
│ │ │ │ └── router.py # /languages endpoints
│ │ │ ├── labels/
│ │ │ │ ├── model.py
│ │ │ │ ├── repository.py
│ │ │ │ ├── schema.py
│ │ │ │ ├── service.py
│ │ │ │ └── router.py # /translation-labels endpoints
│ │ │ └── versions/
│ │ │ ├── model.py
│ │ │ ├── repository.py
│ │ │ ├── schema.py
│ │ │ ├── service.py
│ │ │ └── router.py # /translation-versions endpoints
│ │ │
│ │ └── cms/ # CMS domain
│ │ ├── categories/
│ │ │ ├── model.py
│ │ │ ├── translation_model.py
│ │ │ ├── repository.py
│ │ │ ├── translation_repository.py
│ │ │ ├── schema.py
│ │ │ ├── translation_schema.py
│ │ │ ├── service.py
│ │ │ └── router.py
│ │ ├── tags/
│ │ │ ├── model.py
│ │ │ ├── translation_model.py
│ │ │ ├── repository.py
│ │ │ ├── translation_repository.py
│ │ │ ├── schema.py
│ │ │ ├── translation_schema.py
│ │ │ ├── service.py
│ │ │ └── router.py
│ │ ├── media/
│ │ │ ├── model.py
│ │ │ ├── repository.py
│ │ │ ├── schema.py
│ │ │ ├── service.py
│ │ │ └── router.py
│ │ ├── posts/
│ │ │ ├── model.py # Post model + post_categories, post_tags pivots
│ │ │ ├── translation_model.py
│ │ │ ├── repository.py
│ │ │ ├── translation_repository.py
│ │ │ ├── schema.py
│ │ │ ├── translation_schema.py
│ │ │ ├── public_schema.py
│ │ │ ├── service.py
│ │ │ ├── public_service.py
│ │ │ ├── router.py
│ │ │ └── public_router.py
│ │ ├── pages/
│ │ │ ├── model.py
│ │ │ ├── section_model.py
│ │ │ ├── translation_model.py
│ │ │ ├── repository.py
│ │ │ ├── section_repository.py
│ │ │ ├── translation_repository.py
│ │ │ ├── schema.py
│ │ │ ├── section_schema.py
│ │ │ ├── translation_schema.py
│ │ │ ├── public_schema.py
│ │ │ ├── service.py
│ │ │ ├── section_service.py
│ │ │ ├── public_service.py
│ │ │ ├── router.py
│ │ │ └── public_router.py
│ │ ├── galleries/
│ │ │ ├── model.py
│ │ │ ├── media_model.py
│ │ │ ├── translation_model.py
│ │ │ ├── repository.py
│ │ │ ├── media_repository.py
│ │ │ ├── translation_repository.py
│ │ │ ├── schema.py
│ │ │ ├── media_schema.py
│ │ │ ├── translation_schema.py
│ │ │ ├── public_schema.py
│ │ │ ├── service.py
│ │ │ ├── public_service.py
│ │ │ ├── router.py
│ │ │ └── public_router.py
│ │ ├── sliders/
│ │ │ ├── model.py
│ │ │ ├── repository.py
│ │ │ ├── schema.py
│ │ │ ├── public_schema.py
│ │ │ ├── service.py
│ │ │ ├── public_service.py
│ │ │ ├── router.py
│ │ │ └── public_router.py
│ │ ├── navigation/
│ │ │ ├── model.py
│ │ │ ├── repository.py
│ │ │ ├── schema.py
│ │ │ ├── service.py
│ │ │ └── router.py
│ │ ├── site_settings/
│ │ │ ├── model.py
│ │ │ ├── repository.py
│ │ │ ├── schema.py
│ │ │ ├── service.py
│ │ │ └── router.py
│ │ └── forms/
│ │ ├── model.py
│ │ ├── repository.py
│ │ ├── schema.py
│ │ ├── service.py
│ │ └── router.py
│ └── mkdocs/ # This documentation
├── alembic/
│ ├── env.py # Alembic environment config
│ ├── script.py.mako # Migration template
│ └── versions/ # Generated migration files
├── .env # Local environment (never commit)
├── .env.example # Template for .env
├── alembic.ini
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
Key Conventions
Naming — files are snake_case, classes are PascalCase, constants are UPPER_CASE.
Module structure — each module has its own folder with model.py, repository.py, schema.py, service.py, router.py. Modules with public (unauthenticated) endpoints have separate public_router.py and public_service.py files.
Pivot tables — many-to-many association tables are defined inside the model.py of the owning module (e.g. user_roles in auth/users/model.py, post_categories in cms/posts/model.py).
Imports — every folder has an __init__.py. Always import from the full module path, e.g. from app.features.cms.posts.model import Post.
One responsibility — every file does one thing. Routers contain no logic, services contain no SQL queries, repositories contain no business decisions.
Versioned API — all routes are under /api/v1/. When a breaking change is introduced, /api/v2/ is added in a new router without removing v1.
Public vs admin routes — public (unauthenticated) routes are in public_router.py and are registered before parameterized routes to avoid FastAPI path conflicts.