Skip to content

Backend Code Reference

Purpose

This page explains how the Poolia.BACK codebase is organized internally. It is the human-written counterpart to the autogenerated backend artifacts:

  • backend/generated/models.md
  • backend/generated/router-map.md
  • backend/generated/services-map.md
  • backend/generated/settings.md

Use this page to understand where logic lives, how requests flow through the system, and which modules matter most when making changes.

High-level architecture

The backend follows a layered FastAPI structure:

  1. Routers receive HTTP requests and declare contracts.
  2. Dependencies resolve contextual information such as the current user.
  3. Services enforce business rules and coordinate multi-entity operations.
  4. Repositories handle persistence queries and updates.
  5. Models define MongoDB document shape.
  6. Schemas define API payloads and response DTOs.
  7. Core modules provide configuration, security, and database bootstrap.

This is a conventional and maintainable architecture for a growing internal API.

Request flow

A typical request follows this path:

HTTP request
  -> FastAPI router
  -> Pydantic request schema validation
  -> dependency resolution (optional)
  -> service function
  -> repository access / Beanie document operations
  -> response model serialization

Example: login flow

  • router: app.api.routers.auth
  • service: app.services.auth_service
  • repository: app.repositories.auth_repo
  • security helpers: app.core.security

Example: pool messaging flow

  • router: app.api.routers.messages
  • dependency: app.api.dependencies.auth.get_current_user
  • service: app.services.message_service
  • repositories: message_repo, message_thread_repo, photo_repo, pool_repo
  • model layer: Message, MessageThread, Photo, Pool

Main packages

app.main

The application bootstrap module.

Responsibilities:

  • create the FastAPI app
  • register middleware
  • mount routers
  • initialize Beanie on startup
  • expose the health endpoint

This is the best starting point for understanding the runtime graph.

app.core

config.py

Provides Settings and a module-level settings instance.

db.py

Owns Mongo client creation and Beanie initialization.

Key design choice:

  • Mongo client is lazily instantiated through get_client()

security.py

Contains:

  • SHA-256 prehash + bcrypt password hashing
  • password verification
  • JWT creation and decoding
  • secret and token-expiration constants

app.api.dependencies

auth.py

Defines get_current_user using OAuth2PasswordBearer.

Behavior:

  • extracts bearer token
  • decodes JWT
  • loads the user by ID
  • raises 401 for invalid token or missing user

This is the primary auth gate reused by protected routes.

app.api.routers

Routers are thin and mostly declarative.

auth.py

Exposes register, login, and logout.

users.py

Exposes user CRUD plus authenticated self endpoints.

pools.py

Exposes pool CRUD, alert-config endpoints, dashboard aggregation, technician invite workflow, and pool-scoped visit creation/listing.

pool_values.py

Basic CRUD for water readings.

visits.py

Basic CRUD for visits.

invites.py

CRUD plus accept/decline flows and technician-scoped listings.

photos.py

File-upload and retrieval endpoints for user, visit, and pool photos.

messages.py

The most sophisticated router. It supports:

  • thread creation and listing
  • message creation and editing
  • threaded retrieval
  • photo attachments on messages

app.services

Services contain the actual application behavior.

auth_service.py

Handles registration and login.

Key behaviors:

  • duplicate-email prevention
  • password hashing and verification
  • enforcing active-user login
  • issuing JWTs
  • recording last_login_at

user_service.py

Owns user CRUD and password change logic.

Notable behavior:

  • soft delete instead of hard delete
  • current-password verification before password change

pool_service.py

Owns pool-related business logic.

Key responsibilities:

  • validate owner and technician roles
  • create/update/delete pools
  • remove technician assignments
  • list pools by owner or technician
  • manage alert config
  • build dashboard aggregate response

The dashboard method is a particularly good example of cross-repository orchestration.

pool_values_service.py

Straightforward CRUD around PoolValues.

visit_service.py

Owns visit workflows.

Key behaviors:

  • generic visit CRUD
  • pool-scoped visit listing
  • combined visit + pool-values creation workflow
  • technician-role and pool-assignment validation during combined creation

invite_service.py

Owns invitation lifecycle.

Key behaviors:

  • create invite directly
  • invite technician by email for a pool
  • ensure target user exists and is a technician
  • reject duplicate pending invites
  • accept invite and assign technician to pool
  • decline invite

photo_service.py

Owns upload, replacement, retrieval, and deletion of photo metadata plus blob-storage orchestration.

Key behaviors:

  • profile-photo replacement
  • pool-photo replacement
  • visit-photo upload
  • message-photo upload
  • read URL generation
  • blob deletion on metadata deletion

Notable design detail:

  • Azure blob client access was changed to lazy initialization through get_blob_service() rather than eager module-level creation

That makes the module safer to import from scripts and documentation tooling.

message_service.py

The richest service in the codebase.

Key responsibilities:

  • validate access to pool messaging
  • create threads and initial messages
  • list threads with status filter
  • list messages with attached photos
  • update threads and messages
  • maintain thread cache fields
  • enforce reply-to consistency
  • enforce message edit permissions
  • attach photos to messages

This module contains the clearest example of domain-specific authorization and message-thread lifecycle management.

azure_blob_service.py

Infrastructure adapter for Azure Blob Storage.

This module should remain infrastructure-focused and free of application-specific business rules.

app.repositories

Repositories encapsulate data-access operations. They keep query logic out of services.

Repository modules exist for:

  • users
  • auth
  • pools
  • pool values
  • visits
  • invites
  • photos
  • message threads
  • messages
  • alert config

The repository layer is especially useful in service modules that combine multiple entity reads and updates.

app.models

These are Beanie documents stored in MongoDB.

Main documents:

  • User
  • Pool
  • PoolValues
  • Visit
  • Invite
  • Photo
  • AlertConfig
  • MessageThread
  • Message

Notable model choices:

  • API-friendly aliases such as lastName, ownerId, poolType, createdAt
  • enums and literals to constrain roles, statuses, and message types
  • indexes defined inside Beanie Settings
  • examples included through json_schema_extra

app.schemas

Schemas define the API contract layer.

They are used for:

  • request validation (RegisterRequest, PoolCreate, MessageCreate, etc.)
  • partial updates (UserUpdate, PoolUpdate, VisitUpdate, etc.)
  • response DTOs (TokenResponse, PhotoResponse, PoolDashboardResponse, messaging responses)

A good rule of thumb in this project is:

  • models are persistence-centric
  • schemas are transport-centric

That distinction is not absolute because some routers still return raw document models, but the direction is clear.

Generated documentation scripts

The project now includes scripts under app/scripts to export documentation artifacts such as:

  • OpenAPI JSON
  • OpenAPI Markdown
  • models reference
  • router map
  • services map
  • settings reference

These scripts make the backend codebase self-describing enough to feed the separate documentation repository.

Design strengths

  • Clean layering for a small-to-medium backend
  • Domain logic lives in services instead of routers
  • Beanie models and Pydantic schemas are organized coherently
  • Messaging and photo subsystems demonstrate non-trivial orchestration
  • Documentation generation is colocated with backend code

Areas to watch

  • Authentication is not consistently applied across all routers
  • Response modeling is partly tied to raw document classes
  • Error messaging language is mixed
  • Some naming conventions are not fully normalized across all routes
  • As the project grows, service modules such as message_service.py may benefit from further decomposition

Where to start when changing the code

If you need to change endpoint behavior

Start in the router, then move immediately to the corresponding service.

If you need to change validation or business rules

Start in the service, then inspect repositories and schemas.

If you need to change persistence shape

Start in the model, then inspect schemas, repositories, and service assumptions.

If you need to change auth

Inspect:

  • app.api.dependencies.auth
  • app.core.security
  • app.services.auth_service

If you need to change file upload behavior

Inspect:

  • app.api.routers.photos
  • app.api.routers.messages
  • app.services.photo_service
  • app.services.azure_blob_service
  • app.models.photo

After this page, the most useful autogenerated references are:

  • backend/generated/models.md
  • backend/generated/router-map.md
  • backend/generated/services-map.md
  • backend/generated/settings.md