Welcome to the Movie Theater API project! This educational assignment is designed to help you develop and refine your skills in creating robust web applications using FastAPI, SQLAlchemy, and Docker. Here's what the project offers:
-
Database setup:
- PostgreSQL for development: The application uses PostgreSQL as the main database for the development environment, configured via Docker Compose.
- SQLite for testing: A lightweight SQLite database is utilized for testing, ensuring fast and isolated test execution.
-
Data population:
- The database can be automatically populated with movie data from a provided dataset. This includes associated entities such as genres, actors, languages, and countries, ensuring a rich and interconnected data structure.
-
Docker integration:
- The project is fully Dockerized, allowing seamless setup and execution of the application and its dependencies. Docker Compose simplifies the orchestration of services like the FastAPI application, PostgreSQL database, and any other required components.
-
Project structure:
- A well-organized and modular project structure is provided, including:
- Database models and schemas for movies and related entities.
- Routing logic for managing API endpoints.
- Utility scripts for tasks like data seeding and database migrations.
- A well-organized and modular project structure is provided, including:
The Movie Theater API project follows a modular and organized structure to simplify development, testing, and deployment. Below is an overview of the main components:
Here is a visual representation of the project structure:
.
├── Dockerfile
├── README.MD
├── README_old.md
├── alembic.ini
├── commands
│ ├── run_migration.sh
│ └── run_web_server_dev.sh
├── docker-compose.yml
├── init.sql
├── poetry.lock
├── pyproject.toml
├── pytest.ini
└── src
├── __pycache__
├── config
│ ├── dependencies.py
│ └── settings.py
├── database
│ ├── models
│ │ ├── accounts.py
│ │ ├── base.py
│ │ └── movies.py
│ ├── migrations
│ │ ├── env.py
│ │ └── versions
│ │ ├── 2da0dc469be8_temp_migration.py
│ │ ├── 32b1054a69e3_initial_migration.py
│ │ └── 41cdafa531cf_temp_migration.py
│ ├── populate.py
│ ├── seed_data
│ │ ├── imdb_movies.csv
│ │ └── test_data.csv
│ ├── session_postgresql.py
│ ├── session_sqlite.py
│ └── validators
│ └── accounts.py
├── exceptions
│ └── security.py
├── main.py
├── routes
│ ├── accounts.py
│ └── movies.py
├── schemas
│ ├── accounts.py
│ ├── examples
│ │ └── movies.py
│ └── movies.py
├── security
│ ├── interfaces.py
│ ├── passwords.py
│ ├── token_manager.py
│ └── utils.py
└── tests
├── conftest.py
└── test_integration
├── test_accounts.py
└── test_movies.py
README.MD
: Provides detailed documentation for the project.README_old.md
: Archived version of the original documentation for reference.docker-compose.yml
: Orchestrates the application and its dependencies, such as PostgreSQL, in a development environment.alembic.ini
: Configuration file for Alembic, the tool used for database migrations.poetry.lock
andpyproject.toml
: Manage dependencies and project configurations using Poetry.pytest.ini
: Configuration file for pytest, specifying test settings and options.
run_migration.sh
: A script to execute database migrations.run_web_server_dev.sh
: A script to start the FastAPI development server.
-
config
:dependencies.py
: Defines reusable dependency functions for routes.settings.py
: Contains the project configuration, including database connection details.
-
database
:models
: Organized into separate files for accounts, movies, and base models.populate.py
: Contains logic for populating the database with initial data.session_postgresql.py
: Manages PostgreSQL sessions for development.session_sqlite.py
: Manages SQLite sessions for testing.validators/accounts.py
: Includes validation logic for accounts.
-
exceptions
:security.py
: Custom exceptions for security-related operations.
-
routes
:accounts.py
: Handles endpoints related to user accounts.movies.py
: Handles endpoints for managing movies.
-
schemas
:accounts.py
: Defines schemas for account-related requests and responses.examples/movies.py
: Contains examples for Swagger documentation.
-
security
:interfaces.py
: Provides interfaces for security operations.passwords.py
: Manages password hashing and validation.token_manager.py
: Handles token creation and validation.utils.py
: Contains helper functions for security.
-
tests
:conftest.py
: Defines shared test fixtures.test_integration/
: Contains integration tests for API endpoints.
The get_db
function is a generator that provides a SQLAlchemy session for interacting with the database. This function is particularly useful in FastAPI as it can be injected into route handlers using the Depends
mechanism. Here’s how you can use it effectively:
from fastapi import Depends, APIRouter
from sqlalchemy.orm import Session
from database import get_db # Import the get_db generator
router = APIRouter()
@router.get("/example")
def example_route(db: Session = Depends(get_db)):
# Use the db session here to interact with the database
pass
- The
Depends
function simplifies injecting dependencies like the database session into your route handlers. - The
get_db
function ensures proper session handling: the session is created before the route logic executes and is closed automatically afterward. - This approach promotes cleaner, more testable code by separating dependency setup from business logic.
- Additional dependency injection functions like
get_settings
andget_jwt_auth_manager
can be imported from theconfig
package for other common application needs:
from config import get_settings, get_jwt_auth_manager
You can use this pattern across your application for any routes that need database access or other common dependencies. This modular approach improves code maintainability and testability.
The project is configured with the following services in the docker-compose.yml
file. These services collectively
support the development, testing, and deployment of the Movie Theater API:
- Image:
postgres:latest
- Purpose: Acts as the primary database for the project, running a PostgreSQL instance.
- Configuration:
- Loads initial SQL setup from
init.sql
. - Stores persistent data using a named Docker volume
postgres_theater_data
. - Exposes PostgreSQL on port
5432
.
- Loads initial SQL setup from
- Health Check: Ensures the database is ready by using the
pg_isready
command. - Network: Attached to
theater_network
.
- Image:
dpage/pgadmin4
- Purpose: Provides a web-based interface for managing and monitoring the PostgreSQL database.
- Configuration:
- Exposes pgAdmin on port
3333
. - Stores pgAdmin data in the
pgadmin_theater_data
volume.
- Exposes pgAdmin on port
- Dependency: Starts only after the
db
service is healthy. - Network: Attached to
theater_network
.
- Build Context: Builds the FastAPI application from the local directory (
.
). - Purpose: Runs the FastAPI application, serving the Movie Theater API backend.
- Configuration:
- Exposes the backend on port
8000
. - Uses the
run_web_server_dev.sh
script to start the development server. - Watches for file changes in the
src
directory usingWATCHFILES_FORCE_POLLING=true
. - Mounts the
src
directory to/usr/src/fastapi
inside the container for live development.
- Exposes the backend on port
- Dependency: Starts only after the
db
service is healthy. - Network: Attached to
theater_network
.
- Build Context: Shares the same build context as the backend service.
- Purpose: Runs database migrations using Alembic to ensure the schema is up-to-date.
- Configuration:
- Executes the
run_migration.sh
script to apply migrations. - Uses the
src
directory as the source for migration scripts.
- Executes the
- Dependency: Starts only after the
db
service is healthy. - Network: Attached to
theater_network
.
postgres_theater_data
:- Stores persistent PostgreSQL data.
pgadmin_theater_data
:- Stores persistent data for pgAdmin.
theater_network
:- A bridge network connecting all services, enabling inter-service communication.
The project is configured with the following services in the docker-compose.yml
file. These services collectively
support the development, testing, and deployment of the Movie Theater API:
- Image:
postgres:latest
- Purpose: Acts as the primary database for the project, running a PostgreSQL instance.
- Configuration:
- Loads initial SQL setup from
init.sql
. - Stores persistent data using a named Docker volume
postgres_theater_data
. - Exposes PostgreSQL on port
5432
.
- Loads initial SQL setup from
- Health Check: Ensures the database is ready by using the
pg_isready
command. - Network: Attached to
theater_network
.
- Image:
dpage/pgadmin4
- Purpose: Provides a web-based interface for managing and monitoring the PostgreSQL database.
- Configuration:
- Exposes pgAdmin on port
3333
. - Stores pgAdmin data in the
pgadmin_theater_data
volume.
- Exposes pgAdmin on port
- Dependency: Starts only after the
db
service is healthy. - Network: Attached to
theater_network
.
- Build Context: Builds the FastAPI application from the local directory (
.
). - Purpose: Runs the FastAPI application, serving the Movie Theater API backend.
- Configuration:
- Exposes the backend on port
8000
. - Uses the
run_web_server_dev.sh
script to start the development server. - Watches for file changes in the
src
directory usingWATCHFILES_FORCE_POLLING=true
. - Mounts the
src
directory to/usr/src/fastapi
inside the container for live development.
- Exposes the backend on port
- Dependency: Starts only after the
db
service is healthy. - Network: Attached to
theater_network
.
- Build Context: Shares the same build context as the backend service.
- Purpose: Runs database migrations using Alembic to ensure the schema is up-to-date.
- Configuration:
- Executes the
run_migration.sh
script to apply migrations. - Uses the
src
directory as the source for migration scripts.
- Executes the
- Dependency: Starts only after the
db
service is healthy. - Network: Attached to
theater_network
.
postgres_theater_data
:- Stores persistent PostgreSQL data.
pgadmin_theater_data
:- Stores persistent data for pgAdmin.
theater_network
:- A bridge network connecting all services, enabling inter-service communication.
Follow these steps to set up and run the Movie Theater API project on your local machine.
Start by cloning the project repository from GitHub:
git clone <repository-url>
cd <repository-folder>
It is recommended to use a virtual environment to isolate project dependencies:
# Create a virtual environment
python -m venv venv
# Activate the virtual environment
# On Windows
venv\Scripts\activate
# On macOS/Linux
source venv/bin/activate
This project uses Poetry for dependency management. Install dependencies as follows:
# Install Poetry if not already installed
pip install poetry
# Install project dependencies
poetry install
Create a .env
file in the project root directory with the following variables. Customize the values as needed:
# PostgreSQL
POSTGRES_DB=movies_db
POSTGRES_DB_PORT=5432
POSTGRES_USER=admin
POSTGRES_PASSWORD=some_password
POSTGRES_HOST=postgres_theater
# pgAdmin
PGADMIN_DEFAULT_EMAIL=[email protected]
PGADMIN_DEFAULT_PASSWORD=admin
# JWT keys
SECRET_KEY_ACCESS=838qKq7dGp34hWij3c8txA5ZD2qm9ybt
SECRET_KEY_REFRESH=cFzRk8kllHMW71wQKLXBqDzl24fkhisw
JWT_SIGNING_ALGORITHM=HS256
The project is Dockerized for easy setup. To start all the required services (PostgreSQL, pgAdmin, FastAPI app, and Alembic migrator), run:
docker-compose up --build
Note: On the first run, the database will be populated with data from the dataset. This process may take some time, so please be patient.
- API: The Movie Theater API will be available at
http://localhost:8000
. - pgAdmin: The pgAdmin web interface will be available at
http://localhost:3333
. Use the credentials you defined in the.env
file to log in.
After all services are running, you can test the API by accessing the OpenAPI documentation:
http://localhost:8000/docs
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 Movie Theater 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 (UserGroupEnum
).
-
Relationships:
users
: One-to-many relationship withUserModel
.
-
Constraints:
- Unique constraint on
name
.
- Unique constraint on
Represents application users.
-
Table Name:
users
-
Fields:
id
(Primary Key): Unique identifier for each user.email
: Email address of the user (unique).hashed_password
: Securely stored password hash.is_active
: Boolean indicating whether the user account is active.created_at
: Timestamp when the user was created.updated_at
: Timestamp when the user was last updated.group_id
: Foreign key linking to theuser_groups
table.
-
Relationships:
group
: Links toUserGroupModel
.activation_token
: One-to-one relationship withActivationTokenModel
.password_reset_token
: One-to-one relationship withPasswordResetTokenModel
.refresh_tokens
: One-to-many relationship withRefreshTokenModel
.profile
: One-to-one relationship withUserProfileModel
.
Represents additional information about a user (optional).
-
Table Name:
user_profiles
-
Fields:
id
(Primary Key): Unique identifier for each profile.first_name
: User's first name.last_name
: User's last name.avatar
: Path to the user's avatar image.gender
: Enum value representing the user's gender (GenderEnum
).date_of_birth
: User's date of birth.info
: Additional information about the user.user_id
: Foreign key linking to theusers
table.
-
Relationships:
user
: Links toUserModel
.
-
Constraints:
- Unique constraint on
user_id
.
- Unique constraint on
Abstract base class for all token-based models.
- Fields:
id
(Primary Key): Unique identifier for each token.token
: Securely generated token value.expires_at
: Expiration timestamp for the token.user_id
: Foreign key linking to theusers
table.
Represents tokens used for user account activation.
-
Table Name:
activation_tokens
-
Fields:
- Inherits all fields from
TokenBaseModel
.
- Inherits all fields from
-
Relationships:
user
: Links toUserModel
.
-
Constraints:
- Unique constraint on
user_id
.
- Unique constraint on
Represents tokens used for password reset.
-
Table Name:
password_reset_tokens
-
Fields:
- Inherits all fields from
TokenBaseModel
.
- Inherits all fields from
-
Relationships:
user
: Links toUserModel
.
-
Constraints:
- Unique constraint on
user_id
.
- Unique constraint on
Represents refresh tokens for user authentication.
-
Table Name:
refresh_tokens
-
Fields:
- Inherits all fields from
TokenBaseModel
. token
: Securely generated token value with an extended length.
- Inherits all fields from
-
Relationships:
user
: Links toUserModel
.
-
Methods:
create
: Factory method to simplify the creation of new refresh tokens.
These models handle movies theater functionality.
Represents a movie in the database.
-
Table Name:
movies
-
Fields:
id
(Primary Key): Unique identifier for each movie.name
: Name of the movie.date
: Release date of the movie.score
: Movie rating score (e.g., IMDb score).overview
: A short description or synopsis of the movie.status
: Production status of the movie (e.g., Released, In Production).budget
: The budget of the movie (stored as a decimal value).revenue
: The revenue generated by the movie.country_id
: Foreign key linking to thecountries
table.
-
Relationships:
country
: Links to theCountryModel
.genres
: Many-to-many relationship withGenreModel
.actors
: Many-to-many relationship withActorModel
.languages
: Many-to-many relationship withLanguageModel
.
-
Constraints:
- Unique constraint on
name
anddate
to prevent duplicate entries.
- Unique constraint on
Represents a genre (e.g., Action, Comedy).
-
Table Name:
genres
-
Fields:
id
(Primary Key): Unique identifier for each genre.name
: Name of the genre (e.g., Action, Drama).
-
Relationships:
movies
: Many-to-many relationship withMovieModel
.
Represents an actor in the database.
-
Table Name:
actors
-
Fields:
id
(Primary Key): Unique identifier for each actor.name
: Name of the actor.
-
Relationships:
movies
: Many-to-many relationship withMovieModel
.
Represents a country associated with a movie (e.g., production country).
-
Table Name:
countries
-
Fields:
id
(Primary Key): Unique identifier for each country.code
: ISO 3166-1 alpha-3 country code (e.g., USA, FRA).name
: Full name of the country.
-
Relationships:
movies
: One-to-many relationship withMovieModel
.
Represents a language spoken in a movie.
-
Table Name:
languages
-
Fields:
id
(Primary Key): Unique identifier for each language.name
: Name of the language (e.g., English, French).
-
Relationships:
movies
: Many-to-many relationship withMovieModel
.
Used to establish many-to-many relationships between entities.
-
MoviesGenresModel
:- Links
movies
andgenres
. - Fields:
movie_id
,genre_id
.
- Links
-
ActorsMoviesModel
:- Links
movies
andactors
. - Fields:
movie_id
,actor_id
.
- Links
-
MoviesLanguagesModel
:- Links
movies
andlanguages
. - Fields:
movie_id
,language_id
.
- Links
In this assignment, you are tasked with continuing the development of the cinema application.
The schemas/movies.py
and routes/movies.py
files have already been provided as examples of implementation and do not require any changes. Your objective is to implement the required functionality by filling out the schemas/accounts.py
and routes/accounts.py
files.
These files will handle user-related operations, such as registration, authentication, and account management, while adhering to the structure and design demonstrated in the existing movie-related files.
-
SQLite and Timezone Handling:
- SQLite does not have built-in support for time zones. To ensure compatibility with SQLite during testing, especially for fields like
expires_at
in tokens, you need to manually adjust the timezone. - Example:
expires_at = cast(datetime, token_record.expires_at).replace(tzinfo=timezone.utc)
- This ensures that the datetime object is timezone-aware and allows proper comparisons during tests.
- SQLite does not have built-in support for time zones. To ensure compatibility with SQLite during testing, especially for fields like
-
Mapped Annotations and
cast
:- SQLAlchemy's
Mapped
type hint can sometimes cause IDE warnings or issues when accessing certain attributes. To avoid these, use Python'scast
function to explicitly cast the value. - Example:
reset_token = PasswordResetTokenModel(user_id=cast(int, user.id))
- This approach helps resolve type-related issues, ensuring compatibility with IDEs and type-checking tools like MyPy.
- SQLAlchemy's
-
Adhering to Test Requirements:
- Tests have been pre-written for the application, which means your implementation must align with their expected behavior. Make sure to review and follow the test scenarios carefully.
- Handle edge cases, such as invalid tokens, expired tokens, and unregistered users, to ensure all tests pass successfully.
By following these guidelines and addressing the nuances of SQLite and Mapped
annotations, your implementation will integrate seamlessly into the existing application and meet the expected functionality.
These files will handle user-related operations, such as registration, authentication, and account management, while adhering to the structure and design demonstrated in the existing movie-related files.
Your task is to implement a user registration endpoint in the routes/accounts.py
file. This endpoint allows new users to register by providing their email and password. It should handle potential errors and return the appropriate response based on the scenarios described below.
- HTTP Method:
POST
- Path:
/register/
The endpoint accepts the following request body:
{
"email": "[email protected]",
"password": "SecurePassword123!"
}
If the request is successful, the endpoint returns the following JSON response:
{
"id": 1,
"email": "[email protected]",
}
-
User Creation:
- The endpoint registers a new user and assigns them to the default user group (
UserGroupEnum.USER
). - The password is hashed before storing in the database.
- A new activation token is created for the user, allowing them to activate their account later.
- The endpoint registers a new user and assigns them to the default user group (
-
Error Handling:
- Conflict (409): If a user with the same email already exists, the endpoint returns a
409 Conflict
response with the message:
"A user with this email {user_data.email} already exists."
- Internal Server Error (500): If an unexpected error occurs during user creation, the endpoint returns a
500 Internal Server Error
response with the message:
"An error occurred during user creation."
- Conflict (409): If a user with the same email already exists, the endpoint returns a
-
Data Validation:
- The email and password fields are validated using the
UserRegistrationRequestSchema
schema. - Invalid inputs (e.g., weak passwords, improperly formatted emails) will not pass validation.
- The email and password fields are validated using the
- 201 Created: User registered successfully.
- 409 Conflict: A user with the same email already exists.
- 500 Internal Server Error: An error occurred during user creation.
-
409 Conflict:
{ "detail": "A user with this email [email protected] already exists." }
-
500 Internal Server Error:
{ "detail": "An error occurred during user creation." }
This task requires you to implement the registration logic in routes/accounts.py
. Ensure that the following conditions are met:
-
Database Checks:
- Before creating a user, the database must be checked for an existing email.
-
Token Creation:
- Generate an activation token for every new user.
-
Transaction Handling:
- Ensure proper transaction management. Roll back changes in case of an error.
-
Validation and Testing:
- Validate the functionality against provided test cases, ensuring that all edge cases are handled.
Your task is to implement an endpoint in the routes/accounts.py
file that allows users to activate their accounts by providing a valid activation token and email. The required behavior and response structure are detailed below.
- HTTP Method:
POST
- Path:
/activate/
The endpoint expects a JSON object with the following structure:
{
"email": "[email protected]",
"token": "activation_token"
}
email
(string): The email address associated with the user's account.token
(string): The activation token provided to the user.
- Success Response (200):
- If the token is valid and the user's account is successfully activated, the endpoint returns:
{ "message": "User account activated successfully." }
-
Token Validation:
- The endpoint retrieves the activation token and verifies its validity.
- The token must:
- Belong to the specified user.
- Not be expired.
-
User Activation:
- If the token is valid, the endpoint sets the user's account status to active.
- The activation token is deleted after successful activation.
-
Error Handling:
- 400 Bad Request:
- If the token is invalid or expired, the response includes:
{ "detail": "Invalid or expired activation token." }
- If the user's account is already active, the response includes:
{ "detail": "User account is already active." }
- 400 Bad Request:
- 200 OK: The user's account was successfully activated.
- 400 Bad Request:
- If the activation token is invalid or expired, or if the account is already active.
-
Database Interaction:
- Use the
ActivationTokenModel
table to retrieve and validate the token. - Set
is_active
toTrue
for the user and delete the activation token after successful activation.
- Use the
-
Edge Cases:
- Handle cases where:
- The token is not found or expired.
- The user account is already active.
- Handle cases where:
Your task is to implement an endpoint in the routes/accounts.py
file that allows users to request a password reset token. The endpoint ensures that no sensitive user information is leaked while providing a mechanism to reset passwords securely.
- HTTP Method:
POST
- Path:
/password-reset/request/
The endpoint expects a JSON object with the following structure:
{
"email": "[email protected]"
}
email
(string): The email address associated with the user's account.
- Success Response (200):
- The endpoint always responds with:
{ "message": "If you are registered, you will receive an email with instructions." }
-
User Validation:
- If a user with the provided email exists and is active:
- Any existing password reset tokens for that user are invalidated.
- A new password reset token is generated and stored in the database.
- If the user does not exist or is inactive:
- The same success message is returned to prevent leaking information about the user's existence.
- If a user with the provided email exists and is active:
-
Token Invalidation:
- The endpoint deletes any existing password reset tokens for the user before creating a new one.
-
Always Respond with Success:
- To prevent information leaks, the endpoint always responds with the same success message, regardless of whether the user exists or is active.
- 200 OK: The request was successful. This response is always returned regardless of the user's existence or status.
-
Database Interaction:
- Use the
UserModel
table to verify the user's existence and active status. - Use the
PasswordResetTokenModel
table to manage password reset tokens:- Delete any existing tokens for the user.
- Generate a new token and save it in the database.
- Use the
-
Security Considerations:
- Ensure no information about the user's existence or account status is leaked in the response.
- Validate the email address format to ensure only valid requests are processed.
Your task is to implement the password reset completion functionality in the /reset-password/complete/
endpoint. This endpoint allows users to reset their password using a valid password reset token.
- HTTP Method:
POST
- Path:
/reset-password/complete/
The request body must adhere to the following schema:
-
email
(string, required):- The email address of the user attempting to reset their password.
-
token
(string, required):- The password reset token received during the reset request process.
-
password
(string, required):- The new password for the user.
Example:
{
"email": "[email protected]",
"token": "valid-reset-token",
"password": "NewStrongPassword123!"
}
-
Token Validation:
- The system checks if a valid
PasswordResetTokenModel
exists for the provided email. - The token is verified against the stored record, and its expiration date is validated.
- The system checks if a valid
-
Error Handling:
- If the email or token is invalid, or the token has expired, the system deletes the token (if it exists) and returns an error response with a
400 Bad Request
status code. - If the user does not exist or is inactive, an error response with a
400 Bad Request
status code is returned.
- If the email or token is invalid, or the token has expired, the system deletes the token (if it exists) and returns an error response with a
-
Password Reset:
- Upon successful validation, the user's password is updated in the database.
- The token is deleted after the password is reset to prevent reuse.
-
Error Scenarios:
- If a database error occurs while updating the password, the transaction is rolled back, and a
500 Internal Server Error
is returned.
- If a database error occurs while updating the password, the transaction is rolled back, and a
-
200 OK: Password reset successfully.
- Example Response:
{ "message": "Password reset successfully." }
- Example Response:
-
400 Bad Request: The provided email, token, or password is invalid, or the token has expired.
- Examples:
- Invalid Token:
{ "detail": "Invalid email or token." }
- Expired Token:
{ "detail": "Invalid email or token." }
- Invalid Token:
- Examples:
-
500 Internal Server Error: An unexpected error occurred while resetting the password.
- Example:
{ "detail": "An error occurred while resetting the password." }
- Example:
- Validate the token expiration using a timezone-aware comparison.
- Ensure the password meets all validation requirements before updating.
- Use proper database transaction handling to prevent partial updates.
This task requires careful attention to token validation and password handling to ensure the security and integrity of user data.
Your task is to implement an endpoint that authenticates a user based on their email and password, generates access and refresh tokens upon successful login, and stores the refresh token in the database. Follow the specifications below to ensure the endpoint works as expected.
- HTTP Method:
POST
- Path:
/login/
The endpoint accepts the following JSON payload:
{
"email": "[email protected]",
"password": "UserPassword123!"
}
email
(string): The email address of the user.password
(string): The password associated with the user's account.
The endpoint should return the following JSON response upon successful login:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
access_token
: A JWT used for accessing protected resources.refresh_token
: A JWT used to refresh the access token.token_type
: Specifies the type of token (bearer
).
-
Authentication:
- Validate the user's email and password.
- If the email or password is incorrect, return a
401 Unauthorized
error with the message"Invalid email or password."
.
-
Account Activation:
- If the user's account is not activated, return a
403 Forbidden
error with the message"User account is not activated."
.
- If the user's account is not activated, return a
-
Token Generation:
- Generate a new refresh token using the
jwt_manager.create_refresh_token
method. - Store the refresh token in the
RefreshTokenModel
table, associating it with the user.
- Generate a new refresh token using the
-
Error Handling:
- If an unexpected database error occurs during token storage, return a
500 Internal Server Error
with the message"An error occurred while processing the request."
.
- If an unexpected database error occurs during token storage, return a
-
Success:
- Return both the
access_token
andrefresh_token
in the response.
- Return both the
-
401 Unauthorized:
- Occurs when the email or password is invalid.
- Example response:
{ "detail": "Invalid email or password." }
-
403 Forbidden:
- Occurs when the user's account is not activated.
- Example response:
{ "detail": "User account is not activated." }
-
500 Internal Server Error:
- Occurs when there is an unexpected database error.
- Example response:
{ "detail": "An error occurred while processing the request." }
-
JWT Manager:
- Use the
JWTAuthManagerInterface
to manage token creation and validation. - The
jwt_manager
dependency can be injected into the endpoint using:jwt_manager: JWTAuthManagerInterface = Depends(get_jwt_auth_manager)
- Use the
-
Settings:
- Token-related settings, such as
SECRET_KEY_ACCESS
,SECRET_KEY_REFRESH
, andLOGIN_TIME_DAYS
, can be accessed via:settings: BaseAppSettings = Depends(get_settings)
- Token-related settings, such as
-
Refresh Token Storage:
- Use the
RefreshTokenModel.create
method to create and store the refresh token in the database.
- Use the
- The endpoint successfully authenticates users with valid credentials and returns the correct response.
- The endpoint handles all edge cases (e.g., invalid credentials, inactive account, database errors) and returns appropriate error messages.
- The refresh token is correctly stored in the database, and the tokens are valid according to the provided settings.
Your task is to implement an endpoint in the routes/accounts.py
file that allows users to refresh their access token by providing a valid refresh token.
- HTTP Method:
POST
- Path:
/api/v1/accounts/refresh/
The request body must include the refresh token in the following structure:
{
"refresh_token": "example_refresh_token"
}
Upon successful execution, the endpoint should return a new access token in the following format:
{
"access_token": "new_access_token"
}
-
Token Validation:
- The provided refresh token must be validated using the
JWTAuthManager.decode_refresh_token
method. - If the token is invalid or expired, return a
400 Bad Request
response with an appropriate error message.
- The provided refresh token must be validated using the
-
Refresh Token Existence:
- Check the database to ensure the provided refresh token exists in the
RefreshTokenModel
table. - If the token does not exist, return a
401 Unauthorized
response with the message:"Refresh token not found."
- Check the database to ensure the provided refresh token exists in the
-
User Validation:
- Use the
user_id
extracted from the refresh token to query theUserModel
table. - If the user associated with the token does not exist, return a
404 Not Found
response with the message:"User not found."
- Use the
-
Access Token Generation:
- If all validations pass, generate a new access token using
JWTAuthManager.create_access_token
. - Return the new access token in the response.
- If all validations pass, generate a new access token using
- 200 OK: Access token successfully refreshed.
- Example response:
{ "access_token": "new_access_token" }
- Example response:
- 400 Bad Request: The provided refresh token is invalid or expired.
- Example response:
{ "detail": "Token has expired." }
- Example response:
- 401 Unauthorized: The provided refresh token does not exist in the database.
- Example response:
{ "detail": "Refresh token not found." }
- Example response:
- 404 Not Found: The user associated with the refresh token does not exist.
- Example response:
{ "detail": "User not found." }
- Example response:
-
Dependency Injection:
- Use
jwt_manager: JWTAuthManagerInterface = Depends(get_jwt_auth_manager)
to access the JWT manager for decoding and creating tokens. - Use
db: Session = Depends(get_db)
for database interactions.
- Use
-
Edge Cases:
- Handle tokens that are structurally valid but have been tampered with.
- Ensure the
user_id
extracted from the token matches the user associated with the refresh token in the database.
If you’re unsure about the expected behavior or need clarification, refer to the provided test suite. Running the tests will:
- Show the expected logic and flow for each endpoint.
- Help you identify edge cases and handle errors correctly.
- Ensure your implementation aligns with the project's requirements.
To run the tests, use the following command in the project root directory:
pytest
The test results will indicate any discrepancies between your implementation and the expected behavior, providing clear guidance on how to fix them.