A modular, role-based REST API for managing user accounts, authentication, content moderation, and administration tasks using FastAPI, SQLAlchemy, JWT, Celery, and more.
- Overview
- Features
- Tech Stack
- Project Configuration
- Getting Started
- Docker Configurations
- User Management (via API)
- Project Structure
- Tests
- API Documentation
- Database Models
Online Cinema API is a modular, role-based backend service for managing a movie catalog, user accounts, and administration tasks.
It provides secure user authentication, registration, email verification, and role-based access control.
The platform is designed for scalability with background task processing using Celery and Redis, while offering a flexible REST API built with FastAPI and SQLAlchemy.
Payments are integrated via Stripe, and email notifications are handled through SMTP (Mailhog for testing).
This setup allows both developers and admins to manage content efficiently and securely.
- JWT-based login/logout with access/refresh token flow
- Email-based user activation
- Token blacklisting on logout (via Redis)
- Auto-clean expired tokens using Celery Beat
- User registration and profile management
- Admin privileges to manage users and content
- Role-based access control for sensitive endpoints
- CRUD operations for movies, genres and stars
- Adding/removing movies from carts
- Stripe integration for processing payments
- Webhook handling for payment events
- SMTP email sending (Mailhog for local/testing)
- Email templates with Jinja2 for user notifications
- Celery tasks for sending emails, cleaning tokens, and other async jobs
- Celery Beat for scheduled tasks
- Pytest for automated testing
- Linting (flake8) and type checking (MyPy)
- Dockerized environments for development, testing, and production
- Backend: FastAPI, SQLAlchemy
- Database: PostgreSQL
- Auth: JWT, Redis blacklist
- Background Tasks: Celery + Celery Beat, Redis broker
- Email Service: SMTP (MailHog for local/testing)
- Payments: Stripe
- Testing: Pytest
- Containerization: Docker, Docker Compose (separate configs for prod, test, and dev)
- Infrastructure: AWS EC2 instance for production deployment
- Dependency Management: Poetry
- Type Checking: MyPy
- Linting: flake8
The project uses Poetry for dependency management and configuration.
All Python libraries, dev tools, testing, and type-checking settings are defined here in one place:
[tool.poetry.dependencies]— main runtime dependencies (FastAPI, SQLAlchemy, Celery, Redis, Stripe, etc.)[tool.poetry.group.test.dependencies]— testing libraries (pytest, pytest-mock, pytest-cov)[tool.poetry.group.dev.dependencies]— development tools (flake8, black, mypy, type stubs)[tool.mypy]— mypy type checking configuration[tool.flake8]— linter configuration[tool.pytest.ini_options]— pytest test runner options[tool.alembic]— database migration settings
This setup ensures that all environments (development, testing, production) use consistent dependencies and tool configurations.
By maintaining everything in pyproject.toml, developers can install dependencies with a single command:
poetry installFor the best experience, I recommend using Docker as it handles all dependencies automatically. Local setup with Poetry is provided for development purposes.
git clone https://github.com/Morphin20th/online-cinema.git
cd online-cinema
# 1. Configure environment
cp config_envs/.env.sample .env # Edit with your values
# 2. Start all services (FastAPI, PostgreSQL, Redis, Celery, Mailhog, Stripe CLI)
docker compose -f docker-compose.yml up --buildAccess:
- API:
http://localhost:8001 - Mailhog:
http://localhost:8025
# 1. Install system dependencies
sudo apt install postgresql redis # Ubuntu example
# 2. Set up environment
python -m venv venv && source venv/bin/activate
pip install poetry && poetry install
# 3. Start services
sudo systemctl start postgresql redis
docker compose up mailhog stripe-cli -d # Requires Docker
# 4. Run migrations and server
alembic upgrade head
uvicorn main:app --reload --port 8001The project provides multiple Docker Compose configurations:
- Development (default)
docker compose -f docker-compose.yml up --buildUses .env (copy from config_envs/.env.sample) and starts all services: FastAPI, PostgreSQL, Redis, Celery, Mailhog, Stripe CLI.
Access:
- API:
http://localhost:8001 - Mailhog web UI:
http://localhost:8025
- Production
docker compose --env-file .env.prod -f docker-compose-prod.yml up --buildRequires a .env.prod file with production environment variables. Starts only the services needed for deployment.
- Testing
docker compose --env-file .env.test -f docker-compose-tests.yml up --build --abort-on-container-exit --exit-code-from appRequires a .env.test file. Runs the full test suite in a containerized environment.
Make sure to create the respective
.envfiles before running these commands.
This section explains how to create and authenticate users without exposing real admin credentials. Use localhost for local development or replace with your server URL in production.
POST /accounts/register/
Content-Type: application/json
{
"email": "[user@example.com](mailto:user@example.com)",
"password": "StrongPassword123!"
}
- After registration, an activation email will be sent.
- For local development, check Mailhog at http://localhost:8025 to view the email and click the activation link.
POST /accounts/login/
Content-Type: application/json
{
"email": "user@example.com",
"password": "StrongPassword123!"
}
- The response will include
accessandrefreshtokens. - Use the access token in headers for authenticated requests:
Authorization: Bearer <access_token>
- Always activate the user via the email link before attempting to log in.
- Tokens are used to access protected endpoints according to your role and permissions.
.
├── commands
│ ├── deploy.sh
│ └── run_server_prod.sh
├── config_envs
├── docker
│ ├── prod
│ │ └── Dockerfile
│ └── test
│ └── Dockerfile
├── docker-compose-prod.yml
├── docker-compose-tests.yml
├── docker-compose.yml
├── Dockerfile
├── poetry.lock
├── pyproject.toml
├── README.md
└── src
├── __init__.py
├── main.py
├── config
│ ├── __init__.py
│ ├── api.py
│ ├── celery.py
│ ├── config.py
│ ├── database.py
│ ├── email.py
│ ├── payment.py
│ ├── security.py
│ └── settings.py
├── database
│ ├── __init__.py
│ ├── migrations
│ │ ├── env.py
│ │ ├── script.py.mako
│ │ └── versions
│ │ └── 47d6f267234e_initial_tables.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── accounts.py
│ │ ├── base.py
│ │ ├── carts.py
│ │ ├── enums.py
│ │ ├── movies.py
│ │ ├── orders.py
│ │ ├── payments.py
│ │ └── purchases.py
│ └── session_postgres.py
├── dependencies
│ ├── __init__.py
│ ├── auth.py
│ ├── config.py
│ └── group.py
├── routes
│ ├── __init__.py
│ ├── accounts.py
│ ├── administration.py
│ ├── carts.py
│ ├── docs.py
│ ├── movies
│ │ ├── __init__.py
│ │ ├── genres.py
│ │ ├── movies.py
│ │ ├── movie_utils.py
│ │ └── stars.py
│ ├── orders.py
│ ├── payments.py
│ └── profiles.py
├── schemas
│ ├── __init__.py
│ ├── accounts.py
│ ├── administration.py
│ ├── carts.py
│ ├── common.py
│ ├── examples.py
│ ├── _mixins.py
│ ├── movies.py
│ ├── orders.py
│ ├── payments.py
│ └── profiles.py
├── security
│ ├── __init__.py
│ ├── interfaces.py
│ ├── password.py
│ └── token_manager.py
├── services
│ ├── __init__.py
│ ├── email_interface.py
│ ├── email_service.py
│ ├── stripe_interface.py
│ ├── stripe.py
│ └── templates
│ └── emails
│ ├── activation_confirmation.html
│ ├── activation.html
│ ├── base.html
│ ├── password_reset_completion.html
│ ├── password_reset_request.html
│ └── payment_success.html
├── storage
│ └── media
│ └── avatars
├── tasks_manager
│ ├── __init__.py
│ ├── celery_app.py
│ └── tasks
│ ├── __init__.py
│ └── cleanup.py
├── tests
│ ├── api
│ │ ├── __init__.py
│ │ ├── test_accounts.py
│ │ ├── test_administration.py
│ │ ├── test_carts.py
│ │ ├── test_dependencies.py
│ │ ├── test_docs.py
│ │ ├── test_genres.py
│ │ ├── test_movies.py
│ │ ├── test_orders.py
│ │ ├── test_pagination.py
│ │ ├── test_payments.py
│ │ ├── test_profiles.py
│ │ └── test_stars.py
│ ├── conftest.py
│ ├── examples
│ │ ├── __init__.py
│ │ ├── movie_examples.py
│ │ └── profile_examples.py
│ ├── __init__.py
│ ├── services
│ │ ├── __init__.py
│ │ ├── test_email_service.py
│ │ └── test_stripe_service.py
│ ├── stubs
│ │ ├── email.py
│ │ └── __init__.py
│ ├── tasks
│ │ ├── __init__.py
│ │ └── test_cleanup.py
│ └── utils
│ ├── factories.py
│ ├── fixtures
│ │ ├── carts.py
│ │ ├── clients.py
│ │ ├── __init__.py
│ │ ├── movies.py
│ │ ├── orders.py
│ │ └── payments.py
│ ├── __init__.py
│ └── utils.py
├── utils
│ ├── __init__.py
│ ├── openapi.py
│ ├── pagination.py
│ └── token_generation.py
└── validation
├── __init__.py
├── profile_validators.py
└── security_validators.py
README.MD: Main project documentation.poetry.lock: Poetry-based dependency management.pyproject.toml: Python libraries configurations.docker-compose.yml: Defines and manages multi-container Docker applications (FastAPI app, PostgreSQL, Redis, Celery, Mailhog).docker-compose-tests.yml: Configuration for test environment containers.docker-compose-prod.yml: Configuration for production environment containers.Dockerfile: Builds the FastAPI application image with all dependencies and startup logic.
.env.prod.sample- Sample of variables to use in production environment.env.test.sample- Sample of variables to use in test environment.env.sample- Sample of variables to use in development environment
prod/Dockerfile- Dockerfile for production environment
test/Dockerfile- Dockerfile for test environment
deploy.sh: Automates deployment on the production server. Pulls latest code, resets local repo tomain, builds and starts Docker containers using the production configuration.run_server_prod.sh: Starts the production server locally or on a remote machine using Docker Compose with the production configuration. Ensures all required services are running.
api.py- Configurations for URLsconfig.py- Base configuration loader and environment setupcelery.py- Celery task queue config with Redis broker settingsdatabase.py- Database connection strings and ORM configurationsemail.py- SMTP server settings and email delivery parameterspayment.py- Stripe API keys and payment gateway settingssecurity.py- JWT authentication and password hashing configssettings.py- Core application settings
Each file handles specific service configurations with environment variables.
session_postgres.py: Initializes the SQLAlchemy PostgreSQL session and engine.models/: SQLAlchemy ORM models for Users, Movies, Carts, etc.migrations/: Alembic migration environment and revision files.env.py: Alembic environment configuration.versions/: Individual migration scripts.
auth.py: Authentication and User dependencies.config.py: Project Configuration dependencies.group.py: User groups dependencies.
accounts.py: Endpoints for registration, login, password management, activation.profiles.py: User profile-related routes.administration.py: Admin-only endpoints (user/group management).carts.py: Cart-related endpoints.orders.py: Order-related endpoints.payments.py: Payment-related endpoints.docs.py: Configuration for docs access.movies/: Movie depended endpoints.genres.py: Genre endpoints.movies.py: Movie endpoints.stars.py: Star endpoints.
_mixins.py: Mixins for all schemas to use.accounts.py: Pydantic schemas for auth, login, activation, password reset, etc.profiles.py: Schemas for profile details and updates.administration.py: Schemas related to admin-level operations.common.py: Common schemas used by every route type.movies.py: Movie, genre, star and director schemas.carts.py: Carts schemas.orders.py: Order schemas.payments.py: Payment schemas.examples.py: Examples of HTTPExceptions for OpenAPI.
interfaces.py: JWTAuthManager interface.token_manager.py: Handles JWT token creation, decoding, validation.password.py: Password hashing and verification logic.
email_interface.py: EmailSender interface.email_service.py: Sends email messages (activation, password reset, etc.).stripe_interface.py: Stripe interfacestripe.py: Manages Stripe payment operationstemplates/emails/: HTML templates for various email types.
media/avatars/: Directory for storing uploaded user avatar images.
celery_app.py: Celery application initialization.tasks/: Celery task modules.cleanup.py: Periodically deletes expired tokens (activation/password reset).
profile_validators.py: Custom Pydantic or manual validators for profile data.security_validators.py: Password complexity rules and other auth-related checks.
pagination.py: Pagination link building.token_generation.py: Secure token generation.
conftest.py- Shared pytest fixtures and configurationapi/- API endpoint tests (routes, validation, auth)examples/- Sample data for test casesservices/- Service tests (email, payments)stubs/- Stubs for servicestasks/- Celery task testsutils/- Utilities for tests (helpers, factories)utils/fixtures/- Complex test fixtures (DB models etc.)
This project uses pytest for testing, with coverage reporting and mocks included.
docker compose --env-file .env.test -f docker-compose-tests.yml up --build --abort-on-container-exit --exit-code-from app- Tests are located in
src/tests. - Coverage is measured automatically and reported in the terminal.
- pytest settings are defined in
[tool.pytest.ini_options]inpyproject.toml. - Maximum failures are limited to 1 (
--maxfail=1) for CI efficiency.
This project is deployed to an AWS EC2 instance using Docker Compose and automated via GitHub Actions.
- On
mainbranch push or after a merged pull request, GitHub Actions runs thedeployjob. - The job connects to the EC2 instance via SSH and executes
commands/deploy.sh. - The script pulls the latest code, rebuilds Docker containers, and runs them using
docker-compose-prod.yml.
All API endpoints are automatically documented by FastAPI.
- Swagger UI:
http://localhost:8001/docs/ - ReDoc:
http://localhost:8001/redoc/ - OpenAPI Scheme:
http://localhost:8001/openapi/
Admin privileges required.
The project defines the following entities and relationships using SQLAlchemy. Each entity represents a table in the database and maps to a specific domain concept in the Online Cinema API.
These models handle user authentication, authorization, and related functionality.
Represents user groups in the application (e.g., USER, MODERATOR, ADMIN).
-
Table Name:
user_groups -
Fields:
id(Primary Key): Unique identifier for each user group.name: Enum value representing the group (UserGroupEnumwith values: ADMIN, USER, MODERATOR).
-
Relationships:
users: One-to-many relationship withUserModel.
-
Constraints:
- Unique constraint on
name.
- Unique constraint on
Represents application users with authentication details.
-
Table Name:
users -
Fields:
id(Primary Key): Unique identifier for each user.email: String value representing user's email (max 255 chars, unique, indexed)._hashed_password: Securely stored password hash (max 255 chars).is_active: Boolean indicating whether the user account is active (defaults to False).created_at: Timestamp when the user was created (server default to current time).updated_at: Timestamp when the user was last updated (auto-updates on modification).group_id: Foreign key linking to theuser_groupstable (on delete CASCADE).
-
Relationships:
group: Many-to-one relationship withUserGroupModel.profile: One-to-one relationship withUserProfileModel.activation_token: One-to-one relationship withActivationTokenModel.password_reset_token: One-to-one relationship withPasswordResetTokenModel.refresh_tokens: One-to-many relationship withRefreshTokenModel.cart: One-to-one relationship withCartModel.purchases: One-to-many relationship withPurchaseModel.orders: One-to-many relationship withOrderModel.payments: One-to-many relationship withPaymentModel.
-
Methods:
password: Property (write-only) for setting passwords.verify_password: Verifies if provided password matches the hash.create: Class method for creating new users.
Stores additional user profile information.
-
Table Name:
user_profiles -
Fields:
id(Primary Key): Unique identifier.first_name: Optional string (max 100 chars) for user's first name.last_name: Optional string (max 100 chars) for user's last name.avatar: Optional string (max 255 chars) for avatar URL/path.gender: Optional enum (GenderEnumwith values: MAN, WOMAN).date_of_birth: Optional date field.info: Optional text field for additional information.user_id: Foreign key touserstable (on delete CASCADE, unique).
-
Relationships:
user: One-to-one relationship withUserModel.
Base model for token-based operations (abstract, not directly instantiated).
- Fields:
id(Primary Key): Unique identifier.token: String token value (64 chars by default, unique).expires_at: DateTime when token expires (defaults to 1 day from creation).user_id: Foreign key touserstable (on delete CASCADE).
Handles account activation tokens (inherits from TokenBaseModel).
-
Table Name:
activation_tokens -
Relationships:
user: One-to-one relationship withUserModel.
-
Constraints:
- Unique constraint on
user_id.
- Unique constraint on
-
Methods:
create: Class method for creating activation tokens with custom expiration.
Handles password reset tokens (inherits from TokenBaseModel).
-
Table Name:
password_reset_tokens -
Relationships:
user: One-to-one relationship withUserModel.
-
Constraints:
- Unique constraint on
user_id.
- Unique constraint on
Manages refresh tokens for authentication (inherits from TokenBaseModel).
-
Table Name:
refresh_tokens -
Fields:
token: Extended string token value (512 chars, unique).
-
Relationships:
user: One-to-one relationship withUserModel.
-
Constraints:
- Unique constraint on
user_id.
- Unique constraint on
-
Methods:
create: Class method for creating refresh tokens with custom expiration.
These models handle movie data, including metadata, cast/crew information, and classifications.
Three association tables handle many-to-many relationships between movies and their related entities:
- Table Name:
movies_genres - Fields:
movie_id: Foreign key tomovies.id(on delete CASCADE, primary key)genre_id: Foreign key togenres.id(on delete CASCADE, primary key)
- Table Name:
movies_stars - Fields:
movie_id: Foreign key tomovies.id(on delete CASCADE, primary key)star_id: Foreign key tostars.id(on delete CASCADE, primary key)
- Table Name:
movies_directors - Fields:
movie_id: Foreign key tomovies.id(on delete CASCADE, primary key)director_id: Foreign key todirectors.id(on delete CASCADE, primary key)
Represents movie genres/categories.
-
Table Name:
genres -
Fields:
id(Primary Key): Unique identifiername: Genre name (max 100 chars, unique)
-
Relationships:
movies: Many-to-many relationship withMovieModelthroughMoviesGenresTable
Represents actors/performers in movies.
-
Table Name:
stars -
Fields:
id(Primary Key): Unique identifiername: Star's name (max 100 chars, unique)
-
Relationships:
movies: Many-to-many relationship withMovieModelthroughMoviesStarsTable
Represents movie directors.
-
Table Name:
directors -
Fields:
id(Primary Key): Unique identifiername: Director's name (max 100 chars, unique)
-
Relationships:
movies: Many-to-many relationship withMovieModelthroughMoviesDirectorsTable
Represents age/content ratings for movies (e.g., PG-13, R).
-
Table Name:
certifications -
Fields:
id(Primary Key): Unique identifiername: Certification name (max 100 chars, unique)
-
Relationships:
movies: One-to-many relationship withMovieModel
Main model representing movies in the system.
-
Table Name:
movies -
Fields:
id(Primary Key): Unique identifieruuid: Universally unique identifier (UUID format)name: Movie title (max 250 chars, unique)year: Release yeartime: Runtime in minutesimdb: IMDB rating (float)votes: Number of IMDB votesmeta_score: Optional Metacritic scoregross: Optional box office gross earningsdescription: Full movie description (text)price: Purchase price (DECIMAL(10,2))certification_id: Foreign key tocertificationstable
-
Relationships:
certification: Many-to-one relationship withCertificationModelstars: Many-to-many relationship withStarModelthroughMoviesStarsTablegenres: Many-to-many relationship withGenreModelthroughMoviesGenresTabledirectors: Many-to-many relationship withDirectorModelthroughMoviesDirectorsTablecart_items: One-to-many relationship withCartItemModelpurchases: One-to-many relationship withPurchaseModelorder_items: One-to-many relationship withOrderItemModel
-
Constraints:
- Unique constraint on combination of
name,year, andtime
- Unique constraint on combination of
These models handle shopping cart functionality, allowing users to collect movies before purchase.
Represents a user's shopping cart.
-
Table Name:
carts -
Fields:
id(Primary Key): Unique identifieruser_id: Foreign key tousers.id(on delete CASCADE, unique constraint)
-
Relationships:
user: One-to-one relationship withUserModel(each user has exactly one cart)cart_items: One-to-many relationship withCartItemModel(items in the cart)
Represents individual items in a shopping cart.
-
Table Name:
cart_items -
Fields:
id(Primary Key): Unique identifieradded_at: Timestamp when item was added to cart (server default to current time)cart_id: Foreign key tocarts.id(on delete CASCADE)movie_id: Foreign key tomovies.id(on delete CASCADE)
-
Relationships:
cart: Many-to-one relationship withCartModelmovie: Many-to-one relationship withMovieModel
-
Constraints:
- Unique constraint on combination of
cart_idandmovie_id(prevents duplicate movie entries in the same cart)
- Unique constraint on combination of
Here's the documentation for the Order Models in the requested format:
These models handle order processing and management for movie purchases.
Defines possible order statuses:
PENDING: Order created but not yet paid (default status)PAID: Order successfully paidCANCELLED: Order was cancelled
Represents a customer order containing one or more movies.
-
Table Name:
orders -
Fields:
id(Primary Key): Unique identifieruser_id: Foreign key tousers.id(on delete CASCADE)created_at: Timestamp when order was created (server default to current time)status: Order status (OrderStatusEnum, defaults to PENDING)total_amount: Calculated total order amount (DECIMAL(10,2), nullable)
-
Relationships:
user: Many-to-one relationship withUserModel(order owner)order_items: One-to-many relationship withOrderItemModelpayments: One-to-many relationship withPaymentModel
-
Properties:
total: Computes sum of all movie prices in the order
Represents individual movie items within an order.
-
Table Name:
order_items -
Fields:
id(Primary Key): Unique identifierorder_id: Foreign key toorders.id(on delete CASCADE)movie_id: Foreign key tomovies.id(on delete CASCADE)
-
Relationships:
order: Many-to-one relationship withOrderModelmovie: Many-to-one relationship withMovieModelpayment_items: One-to-many relationship withPaymentItemModel
This model handles movie purchase records, tracking which users have purchased which movies.
Represents a movie purchase by a user.
-
Table Name:
purchases -
Fields:
id(Primary Key): Unique identifierpurchased_at: Timestamp when purchase was made (server default to current time)user_id: Foreign key tousers.id(on delete CASCADE)movie_id: Foreign key tomovies.id(on delete CASCADE)
-
Relationships:
user: Many-to-one relationship withUserModel(links to purchasing user)movie: Many-to-one relationship withMovieModel(links to purchased movie)
-
Constraints:
- Unique constraint on combination of
user_idandmovie_id(prevents duplicate purchases of same movie by same user)
- Unique constraint on combination of
These models handle payment processing and transaction records for movie purchases.
Defines possible payment statuses:
SUCCESSFUL: Payment completed successfullyCANCELLED: Payment was cancelledREFUNDED: Payment was refunded
Represents a payment transaction.
-
Table Name:
payments -
Fields:
id(Primary Key): Unique identifiercreated_at: Timestamp when payment was created (server default to current time)amount: Total payment amount (DECIMAL(10,2))status: Payment status (PaymentStatusEnum, defaults to SUCCESSFUL)external_payment_id: Optional reference to external payment processor ID (max 255 chars)user_id: Foreign key tousers.id(on delete CASCADE)order_id: Foreign key toorders.id(on delete CASCADE)
-
Relationships:
user: Many-to-one relationship withUserModel(payer)order: Many-to-one relationship withOrderModelpayment_items: One-to-many relationship withPaymentItemModel
Represents individual items within a payment (line items).
-
Table Name:
payment_items -
Fields:
id(Primary Key): Unique identifierprice_at_payment: Snapshot of item price at time of payment (DECIMAL(10,2))payment_id: Foreign key topayments.id(on delete CASCADE)order_item_id: Foreign key toorder_items.id(on delete CASCADE)
-
Relationships:
payment: Many-to-one relationship withPaymentModelorder_item: Many-to-one relationship withOrderItemModel