MOOC Utils

Utils like auto assignment solver extension and course notice announcment notification for MOOC courses

Data models and schema

Introduction#

Overview of persistence and API contracts in notice-reminders: Tortoise models, Pydantic schemas, and how they line up.

Project structure#

The data layer is implemented using Tortoise ORM within the notice-reminders application. Models are defined under app/models and registered with FastAPI via app/core/database.py. API schemas are defined under app/schemas and are used to validate and serialize requests and responses.

Core components#

This section documents each database entity, including fields, data types, primary keys, foreign keys, indexes, and constraints. It also explains how the models are registered and initialized.

  • User

  • Fields: id (IntField, pk), email (CharField, unique, indexed), name (CharField, nullable), telegram_id (CharField, unique, nullable), is_active (BooleanField), created_at (DatetimeField), updated_at (DatetimeField)

  • Indexes: email, telegram_id

  • Constraints: unique(email), unique(telegram_id)

  • Table: users

  • Course

  • Fields: id (IntField, pk), code (CharField, unique, indexed), title (CharField), url (CharField), instructor (CharField), institute (CharField), nc_code (CharField), created_at (DatetimeField), updated_at (DatetimeField)

  • Indexes: code

  • Constraints: unique(code)

  • Table: courses

  • Announcement

  • Fields: id (IntField, pk), course (ForeignKey to Course, related_name: announcements), title (CharField), date (CharField), content (TextField), fetched_at (DatetimeField)

  • Table: announcements

  • Subscription

  • Fields: id (IntField, pk), user (ForeignKey to User, related_name: subscriptions), course (ForeignKey to Course, related_name: subscriptions), created_at (DatetimeField), is_active (BooleanField)

  • Constraints: unique(user, course)

  • Table: subscriptions

  • NotificationChannel

  • Fields: id (IntField, pk), user (ForeignKey to User, related_name: notification_channels), channel (CharField), address (CharField), is_active (BooleanField), created_at (DatetimeField)

  • Constraints: unique(user, channel, address)

  • Table: notification_channels

  • Notification

  • Fields: id (IntField, pk), user (ForeignKey to User, related_name: notifications), subscription (ForeignKey to Subscription, related_name: notifications), announcement (ForeignKey to Announcement, related_name: notifications), channel (ForeignKey to NotificationChannel, nullable, related_name: notifications), sent_at (DatetimeField), is_read (BooleanField)

  • Table: notifications

  • OtpCode

  • Fields: id (IntField, pk), email (CharField, indexed), code (CharField), expires_at (DatetimeField), is_used (BooleanField), created_at (DatetimeField)

  • Indexes: email

  • Table: otp_codes

  • RefreshToken

  • Fields: id (IntField, pk), user (ForeignKey to User, related_name: refresh_tokens, on_delete=CASCADE), token (CharField, unique, indexed), expires_at (DatetimeField), is_revoked (BooleanField), created_at (DatetimeField)

  • Indexes: token

  • Constraints: unique(token)

  • Table: refresh_tokens

Architecture overview#

The data model architecture follows a relational design with explicit foreign key relationships. Tortoise ORM is configured to auto-generate schemas when the SQLite file does not exist and registers all models with the FastAPI application.

Detailed component analysis#

User model#

  • Purpose: Stores user account information and profile metadata.
  • Key constraints: unique(email), unique(telegram_id)
    • Indexes: email, telegram_id
  • Related models: Subscriptions, NotificationChannels, Notifications, RefreshTokens

Course model#

  • Purpose: Represents MOOC courses with metadata.
  • Key constraints: unique(code)
    • Indexes: code
  • Related models: Announcements, Subscriptions

Announcement model#

  • Purpose: Stores scraped course announcements with course linkage.
  • Related model: Notifications
  • Notes: fetched_at indicates when the record was inserted after scraping.

Subscription model#

  • Purpose: Links users to courses they follow.
  • Unique constraint: (user, course)
  • Related models: Notifications

NotificationChannel model#

  • Purpose: Defines per-user channels (e.g., Telegram) for delivery.
  • Unique constraint: (user, channel, address)
  • Related model: Notifications

Notification model#

  • Purpose: Records sent notifications with optional channel association and read state.
  • Foreign keys: user, subscription, announcement, channel (nullable)
  • Related models: User, Subscription, Announcement, NotificationChannel

OtpCode model#

  • Purpose: Stores OTP codes for email-based authentication.
    • Indexes: email
  • Lifecycle: expires_at determines validity; is_used marks consumption.

RefreshToken model#

  • Purpose: Manages refresh tokens for secure sessions.
    • Constraints: unique(token)
  • Behavior: CASCADE delete on user removal; is_revoked flag supports revocation.

Dependency analysis#

The models form a connected relational graph. The following diagram highlights foreign key dependencies and uniqueness constraints.

Performance considerations#

  • Indexes: email and telegram_id on User; code on Course; email on OtpCode; token on RefreshToken. These support frequent lookups and uniqueness checks.
  • Unique constraints: reduce duplicate entries and simplify join conditions.
  • Auto timestamps: created_at and updated_at enable efficient sorting and filtering.
  • Recommendations:
  • Add composite indexes for frequently filtered pairs (e.g., user+is_active on Subscription).
  • Consider partitioning or soft-deleted views for Notification if volume grows large.
  • Use pagination and limit clauses in queries to avoid large result sets.

Troubleshooting guide#

  • Registration and schema generation:
  • The database is registered via a configuration that lists all models. If the SQLite file does not exist, schemas are generated automatically.
  • Ensure the database URL points to a writable path for SQLite.
  • Common issues:
  • Integrity errors on unique fields (email, telegram_id, code, token): validate inputs before creation.
  • Expiration handling: ensure background jobs or scheduled tasks invalidate expired OTPs and refresh tokens.
  • Cascade deletes: removing a user will remove dependent refresh tokens; confirm intended behavior.

Conclusion#

Models persist, schemas validate, services decide. Keep those roles from collapsing into the routers.

Appendices#

API schemas and validation rules#

  • UserResponse mirrors User fields and enables ORM-to-Pydantic conversion.
  • CourseResponse mirrors Course fields.
  • AnnouncementResponse exposes course_id for clarity.
  • SubscriptionCreate accepts course_code; SubscriptionResponse exposes user_id and course_id.
  • NotificationResponse exposes channel_id as optional.

These schemas enforce type safety and validation for incoming/outgoing data.

Data access patterns and business rules#

  • User CRUD: update profile fields; toggle is_active; link Telegram ID.
  • Course lookup: filter by code for subscription creation.
  • Subscription lifecycle: create unique (user, course); toggle is_active; cascade reads into notifications.
  • Notification lifecycle: mark is_read; associate channel for delivery; track sent_at.
  • OTP lifecycle: generate with expiry; mark is_used upon verification; reject expired codes.
  • Refresh token lifecycle: generate with expiry; revoke by setting is_revoked; enforce CASCADE on user deletion.

Migration considerations#

  • Initial schema generation occurs when the SQLite file is missing; subsequent runs reuse existing tables.
  • For production databases (PostgreSQL/MySQL), use Tortoise migrations to evolve schema safely.
  • When altering unique constraints or indexes, plan downtime or use online DDL where supported.
  • Back up before major schema changes; test rollback procedures.