The Lodge Management System is a modern, API-first backend designed to facilitate property management for landlords. Its primary function is to track physical assets (Lodges and Rooms), manage occupancy (Tenants and Leases), and handle financial oversight (Rent Payments). The architecture has evolved from a simple monolithic CRUD application into a robust, domain-driven service architecture, prepared for multi-tenancy and complex business logic.
Core Tech Stack:
- Framework: FastAPI (Python 3.10+) - Chosen for its high performance, asynchronous support, and automatic OpenAPI documentation.
- Database ORM: SQLAlchemy 2.0 - Utilized for defining data models, enforcing constraints, and managing complex table relationships.
- Data Validation: Pydantic V2 - Ensures strict type-checking and serialization/deserialization of API payloads.
- Authentication: OAuth2 with JWT (JSON Web Tokens) - Provides stateless, scalable, and secure session management via
passlib(bcrypt). - Database Engine: SQLite (configured for local MVP development, heavily abstracted for easy migration to PostgreSQL).
- Migrations: Alembic - Tracks schema changes over time.
The project strictly adheres to a Layered Architecture, heavily influenced by Domain-Driven Design (DDD) principles. Specifically, it employs the Service-Repository Pattern. This pattern ensures that the API routers (Presentation), the business rules (Service), and the database queries (Repository/CRUD) remain highly decoupled.
Directory Mapping (app/):
api/v1/: Presentation Layer. Contains the FastAPI routers. These are "thin" controllers.services/: Business Logic Layer. Contains the "Orchestrators". This is where validation, multi-table transactions, and domain rules live.crud/: Data Access Layer (Repository). Contains the "Workers". Strictly limited to direct SQLAlchemy database operations.models/: Domain Entities. The physical representation of data structures in the SQL database.schemas/: Data Transfer Objects (DTOs). Pydantic models defining the exact shape of JSON entering and leaving the API.core/: Application-wide settings, security protocols, and global custom exceptions.db/: Database connection pooling and session management.
- Responsibility: To accept incoming HTTP requests, enforce authentication via Dependency Injection (
Depends(get_landlord_user)), hand off the payload to the Service Layer, and translate any resulting data or custom exceptions into standardized HTTP responses (e.g., 200 OK, 404 Not Found, 400 Bad Request). - Design Choice: Hierarchical Routing. Routes are structured to reflect resource ownership (e.g.,
GET /lodges/{lodge_id}/roomsrather than a globalGET /rooms). This prevents route collisions and enforces logical boundaries.
- Responsibility: Acts as the "Head Chef". It orchestrates multiple CRUD calls to fulfill a single business request.
- Key Implementations:
- Authorization: Explicit checks verify ownership before action (e.g.,
lodge_service.verify_lodge_ownership). - Atomic Transactions: Operations that affect multiple tables (like registering a tenant, which creates a
Userand aTenantProfile) use manual transaction management (db.flush()anddb.rollback()) to prevent orphaned records. - Exception Bubbling: When business rules fail (e.g., "Room is already occupied"), services raise explicit custom Python exceptions (
ActiveLeaseFoundError) rather than returningNone.
- Authorization: Explicit checks verify ownership before action (e.g.,
- Responsibility: Execution of raw database queries.
- Key Implementations:
CRUDBase: A generic class providing standardget,create, andupdatefunctionality, enforcing the DRY principle.- Explicit Overrides: When complex filtering is required (e.g.,
get_active_room_and_tenant_lease), specific methods are written in the domain's CRUD file. The project favors explicit DB queries over loading massive relationships into memory for filtering.
- Responsibility: Defining the input/output contract of the API.
- Key Implementations:
- Composition over Inheritance: Schemas like
TenantProfileResponsecorrectly nestUserResponseobjects rather than inheriting all their fields, preventing data redundancy. - Security: Output schemas explicitly exclude sensitive fields like
hashed_password.
- Composition over Inheritance: Schemas like
- Client Request: Frontend sends a
POST /api/v1/leases/request with a JSON payload containingroom_id,tenant_id, andagreed_rent_amt. - Presentation (Router):
app/api/v1/leases.pyreceives the request. FastAPI validates the JSON againstschema_lease.LeaseCreate. It also injects thelandlord_uservia the authentication dependency. - Service (Orchestrator): The router calls
lease_services.create_new_lease(...). - Validation (Service -> CRUD): The service queries
crud_roomandcrud_tenantto ensure both entities exist. It then checks ifroom.lodge.landlord_idmatches the current user. Finally, it queriescrud_leaseto ensure the room is not already occupied (ActiveLeaseFoundError). - Execution (CRUD): If all rules pass, the service calls
crud_lease.create_lease(). - Database Commit: The CRUD layer instantiates the SQLAlchemy model, updates the Room status to OCCUPIED, and calls
db.commit(). - Response: The newly created Lease object bubbles back up to the router, where FastAPI uses
schema_lease.LeaseResponseto serialize it into JSON for the client.
- Authentication: Stateless. A successful login (
POST /api/v1/auth/login) returns a JWT. Subsequent requests must include this token in theAuthorization: Bearerheader. - Role-Based Access Control (RBAC): Implemented cleanly via Dependency Injection. Endpoints are protected by specific functions like
get_landlord_userorget_tenant_user(inapp/api/deps.py), which decode the JWT and check theUser.roleattribute before allowing route execution. - Error Handling: The project successfully avoids "Leaky Abstractions". The CRUD layer returns models or
None. The Service layer interpretsNoneas a business failure and raises a Custom Exception (defined inapp/core/exceptions.py). The API Router usestry...exceptblocks to catch these specific exceptions and throw user-friendlyHTTPExceptionresponses.
Based on the current audit, the project has a strong foundation, but requires immediate attention in a few key areas to ensure scalability and data integrity:
-
Recommendation 1: Fix Transaction Management in Lease Creation.
- Issue: In
app/crud/lease.py(assuming the commented code is the implementation),create_leasealters both theLeasetable and theRoomtable's status. If an error occurs between thedb.add(db_lease)anddb.commit(), it could lead to inconsistent states. - Action: Refactor this to use the same atomic transaction pattern implemented in
user_service.py. The Service Layer should manage the state change of the room and the creation of the lease, usingtry...exceptanddb.rollback()to ensure both succeed or both fail.
- Issue: In
-
Recommendation 2: Standardize the
update_room_detailsService.- Issue: In
app/services/room_service.py,update_room_detailssuccessfully fetches the room but currently returns early (return lodge), leaving the actualcrud_room.update()call as unreachable dead code. Furthermore, it lacks the explicit ownership verification check (room.lodge.landlord_id != landlord_id) before updating. - Action: Remove the early return. Implement the ownership check. Ensure the
update_datapayload is properly passed to the CRUD layer.
- Issue: In
-
Recommendation 3: Consolidate Route Design in Leases & Tenants.
- Issue: The route
POST /api/v1/leases/does not include thelodge_idin the URL hierarchy, whileGET /api/v1/leases/{lodge_id}does. This inconsistency makes the API harder to consume. - Action: Standardize route paths. If a lease strictly belongs to a room, and a room belongs to a lodge, consider
POST /api/v1/lodges/{lodge_id}/leasesto ensure the context is always present and the landlord's ownership of the building can be verified immediately at the router/service boundary.
- Issue: The route
(The PM AI is configured to maintain a detailed Todo.md file, which tracks Epic-level tasks. Note: Some task tracking files have been moved to .gitignore to prevent cluttering the repository).
Current Priorities:
- Task 5.1: Fix unreachable code in
app/services/room_service.py(update_room_details). - Task 5.2: Refactor
app/api/v1/leases.pyto use{lodge_id}hierarchy forPOSTroutes. - Task 5.3: Refactor
crud_lease.pyorlease_services.pyto handle lease creation and room status updates via a single atomicdb.commit().