Architecture overview
Introduction#
Layout of the Notice Reminders API: FastAPI app factory, routers, services, Tortoise persistence, and external scrapers. Auth cookies and CORS config sit at the edges.
Project structure#
The Notice Reminders project is organized as a FastAPI application under the notice-reminders package. The structure separates concerns into:
- Application entry points for CLI and API modes
- Core configuration, database registration, and dependency injection
- API routers grouped by domain resources
- Services for scraping and business logic
- Data models managed via Tortoise ORM
- Pydantic settings for environment-driven configuration
graph TB subgraph "notice-reminders/" A["main.py<br/>Entry point for CLI/API modes"] B["app/api/main.py<br/>FastAPI app factory"] C["app/core/config.py<br/>Settings"] D["app/core/database.py<br/>Tortoise registration"] E["app/core/dependencies.py<br/>DI factories"] F["app/core/auth.py<br/>Auth helpers"] G["app/api/routers/*<br/>Resource routers"] H["app/services/*<br/>Business services"] I["app/models/*<br/>ORM models"] J["pyproject.toml<br/>Dependencies and scripts"] K["README.md<br/>Usage and architecture"] end A --> B B --> C B --> D B --> G E --> H H --> I C --> H
Core components#
- Application Factory: The FastAPI application is created via a factory function that configures middleware, registers routers, and initializes the database.
- Middleware: CORS is configured using environment-driven origins.
- Dependency Injection: LRU-cached factories supply services and settings to route handlers.
- Database Registration: Tortoise ORM is registered with configurable schema generation and SQLite path provisioning.
- Authentication: Cookie-based JWT tokens with helpers for current user resolution and decorator-based enforcement.
Key implementation references:
- Application factory and router registration:
notice-reminders/app/api/main.py - CORS configuration:
notice-reminders/app/api/main.py - Settings and environment loading:
notice-reminders/app/core/config.py - DI factories:
notice-reminders/app/core/dependencies.py - Database registration:
notice-reminders/app/core/database.py - Auth helpers:
notice-reminders/app/core/auth.py
Architecture overview#
The system follows a layered architecture:
- Entry Point: A unified CLI/API entry chooses between modes.
- Web Layer: FastAPI app factory configures middleware and routes.
- Domain Services: Business logic for authentication, subscriptions, courses, and scraping.
- Persistence: Tortoise ORM models and migrations.
- External Integrations: Scraping Swayam and future NPTEL integrations.
graph TB EP["notice-reminders/main.py<br/>CLI/API entry"] APP["app/api/main.py<br/>create_app()"] CORS["CORS Middleware"] R_USERS["Routers: users, auth, subscriptions,..."] DI["app/core/dependencies.py<br/>Factories"] CFG["app/core/config.py<br/>Settings"] DB["app/core/database.py<br/>Tortoise registration"] SVC_SWAYAM["app/services/swayam_service.py"] MODELS["app/models/*.py"] EP --> APP APP --> CORS APP --> R_USERS APP --> DB R_USERS --> DI DI --> CFG DI --> SVC_SWAYAM SVC_SWAYAM --> MODELS DB --> MODELS
Detailed component analysis#
Application factory pattern and routing#
- The application factory constructs a FastAPI app, sets title and debug flags from settings, registers CORS middleware, includes all resource routers, and registers the database.
- Routers are grouped by domain: users, auth, subscriptions, courses, announcements, notifications, and search.
sequenceDiagram participant EP as "Entry Point" participant APP as "create_app()" participant FA as "FastAPI" participant CORS as "CORS Middleware" participant R as "Routers" participant DB as "register_database" EP->>APP : "Invoke create_app()" APP->>FA : "Initialize app(title, debug)" APP->>CORS : "Add middleware(origins, credentials, headers)" APP->>R : "include_router(...) for each router" APP->>DB : "register_database(url, generate_schemas)" APP-->>EP : "Return app"
Middleware configuration (CORS)#
- CORS is configured using environment-driven origins from settings, allowing credentials and all methods/headers.
Implementation references:
- Middleware registration:
notice-reminders/app/api/main.py - Origins from settings:
notice-reminders/app/core/config.py
Dependency injection pattern#
- Factories decorated with caching supply services and settings to route handlers.
- Typical chain: route handler -> service -> external client (e.g., SwayamService).
classDiagram
class Settings {
+app_name
+debug
+database_url
+cors_origins
+jwt_* settings
}
class SwayamService {
+search_courses(query)
+get_announcements(code)
}
class UserService
class CourseService
class SubscriptionService
class NotificationService
class NotificationChannelService
class OtpEmailService
class AuthService
class DI_Factories {
+get_settings()
+get_swayam_service()
+get_course_service()
+get_announcement_service()
+get_user_service()
+get_subscription_service()
+get_notification_service()
+get_notification_channel_service()
+get_otp_email_service()
+get_auth_service()
}
DI_Factories --> Settings : "Depends(get_settings)"
DI_Factories --> SwayamService : "Depends(get_swayam_service)"
DI_Factories --> UserService
DI_Factories --> CourseService
DI_Factories --> SubscriptionService
DI_Factories --> NotificationService
DI_Factories --> NotificationChannelService
DI_Factories --> OtpEmailService
DI_Factories --> AuthServiceDatabase registration process#
- Tortoise configuration defines connections and apps mapping models from the models package.
- SQLite path provisioning creates directories before schema generation.
- Schema generation is controlled by debug flag and SQLite path existence.
flowchart TD
Start(["register_database called"]) --> BuildCfg["Build Tortoise config"]
BuildCfg --> DetectSQLite["Detect SQLite path from URL"]
DetectSQLite --> Exists{"Path exists?"}
Exists --> |No| MkDirs["Create parent dirs"]
Exists --> |Yes| SkipDirs["Skip mkdir"]
MkDirs --> GenFlag["Set generate_schemas = true"]
SkipDirs --> KeepFlag["Keep generate_schemas"]
GenFlag --> Register["register_tortoise(app, config, generate_schemas)"]
KeepFlag --> Register
Register --> End(["Done"])Authentication and authorization#
- Token verification resolves current user from cookies.
- Decorator enforces authentication and injects current_user into route handlers.
- Auth endpoints manage OTP requests, verification, token refresh, and logout.
sequenceDiagram participant Client as "Client" participant AuthRouter as "auth.py" participant DI as "get_auth_service" participant AS as "AuthService" participant Cookies as "HTTP Cookies" Client->>AuthRouter : "POST /auth/request-otp" AuthRouter->>DI : "get_auth_service()" DI-->>AuthRouter : "AuthService instance" AuthRouter->>AS : "request_otp(email)" AS-->>AuthRouter : "expires_at" AuthRouter-->>Client : "OtpRequestResponse" Client->>AuthRouter : "POST /auth/verify-otp" AuthRouter->>AS : "verify_otp(email, code)" AS-->>AuthRouter : "user, is_new_user" AuthRouter->>Cookies : "Set access_token, refresh_token" AuthRouter-->>Client : "AuthStatus" Client->>AuthRouter : "GET /auth/me" AuthRouter->>AS : "get_current_user()" AS-->>AuthRouter : "User" AuthRouter-->>Client : "UserResponse"
Routing architecture#
- Users: CRUD and notification channel management with per-user authorization checks.
- Subscriptions: Create/list/delete subscriptions linked to courses.
- Auth: OTP lifecycle, token refresh, logout, and profile retrieval.
graph LR
U["users.py<br/>GET/PATCH/DELETE /{user_id}<br/>POST /{user_id}/channels<br/>GET /{user_id}/channels"]
S["subscriptions.py<br/>POST /<br/>GET /<br/>DELETE /{id}"]
A["auth.py<br/>/request-otp<br/>/verify-otp<br/>/refresh<br/>/logout<br/>/me"]
U --> |"Depends(get_user_service)"| USvc["UserService"]
U --> |"Depends(get_notification_channel_service)"| NCSvc["NotificationChannelService"]
S --> |"Depends(get_subscription_service)"| SubSvc["SubscriptionService"]
S --> |"Depends(get_course_service)"| CSvc["CourseService"]
A --> |"Depends(get_auth_service)"| ASvc["AuthService"]Data models and relationships#
- User, Course, and Subscription define primary entities and relationships.
- Unique constraints prevent duplicate subscriptions per user-course pair.
erDiagram
USER {
int id PK
string email UK
string name
string telegram_id UK
boolean is_active
datetime created_at
datetime updated_at
}
COURSE {
int id PK
string code UK
string title
string url
string instructor
string institute
string nc_code
datetime created_at
datetime updated_at
}
SUBSCRIPTION {
int id PK
int user_id FK
int course_id FK
boolean is_active
datetime created_at
}
USER ||--o{ SUBSCRIPTION : "has"
COURSE ||--o{ SUBSCRIPTION : "has"External service integration (swayam and NPTEL)#
- SwayamService encapsulates scraping operations for course search and announcements.
- NPTEL base URL is configured for potential future integration.
graph TB SW["SwayamService"] SCR["SwayamScraper<br/>(in scrapers)"] CFG["Settings<br/>swayam_base_url, nptel_base_url"] SW --> SCR SW --> CFG
Dependency analysis#
- External libraries include FastAPI, Uvicorn, Tortoise ORM, Aerich, Pydantic Settings, httpx, beautifulsoup4, PyJWT, and multipart parsing.
- The API entry point exposes a console script for unified CLI/API invocation.
graph LR P["pyproject.toml"] F["fastapi"] U["uvicorn"] T["tortoise-orm"] A["aerich"] PS["pydantic-settings"] HV["httpx"] BS["beautifulsoup4"] JWT["PyJWT"] ME["python-multipart"] P --> F P --> U P --> T P --> A P --> PS P --> HV P --> BS P --> JWT P --> ME
Performance considerations#
- Caching: LRU caches for settings and services reduce repeated instantiation overhead.
- Database: SQLite is suitable for small-scale usage; consider PostgreSQL for production scale.
- CORS: Allow only necessary origins to minimize preflight overhead.
- Token TTL: Short-lived access tokens with refresh tokens improve security and reduce long-lived credential exposure.
- Scraping: Introduce rate limiting and caching for Swayam/NPTEL endpoints to avoid overloading external services.
Troubleshooting guide#
- CORS errors: Verify allowed origins in settings match frontend origin.
- Database initialization: Ensure SQLite path exists or enable schema generation in debug mode.
- Authentication failures: Confirm cookie presence and token validity; check token expiry and signing secret.
- Route access denied: Ensure current user matches resource ownership and permissions.
Conclusion#
App factory, routers, services, ORM. Keep scraping in services so routers stay HTTP-shaped.
Appendices#
Deployment considerations#
- Environment variables: Configure settings via .env for database URL, JWT secrets, SMTP, and CORS origins.
- Database: Use PostgreSQL in production; ensure migrations are applied via Aerich.
- Reverse proxy: Place behind Nginx or equivalent with HTTPS termination.
- Scaling: Stateless API pods behind a load balancer; persist sessions via Redis if needed.