Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions .env.sample

This file was deleted.

38 changes: 38 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Deploy to EC2

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Deploy via SSH
uses: appleboy/ssh-action@v0.1.9
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: 22
script: |
cd ~/py-fastapi-homework-5-ec2-deploy-task
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart fastapi.backend
26 changes: 20 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:

- name: Run accounts tests
run: |
export PYTHONPATH=$(pwd)/src
poetry run pytest src/tests/test_integration/test_accounts.py

test-movies:
Expand All @@ -54,6 +55,7 @@ jobs:

- name: Run movies tests
run: |
export PYTHONPATH=$(pwd)/src
poetry run pytest src/tests/test_integration/test_movies.py

test-profiles:
Expand All @@ -77,21 +79,33 @@ jobs:
run: |
poetry run flake8 src

- name: Run movies tests
- name: Run profiles tests
run: |
export PYTHONPATH=$(pwd)/src
poetry run pytest src/tests/test_integration/test_profiles.py

test-e2e:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Start Docker Compose and wait for completion
run: |
docker compose -f docker-compose-tests.yml up --build --abort-on-container-exit --exit-code-from web
- name: Set up Docker Compose
run: docker compose version

- name: List test_e2e folder
run: ls -la src/tests/test_e2e

- name: List all files
run: ls -la

- name: Check Docker Compose config
run: docker compose -f docker-compose-tests.yml config

- name: Build and run E2E tests
run: docker compose -f docker-compose-tests.yml up --build --abort-on-container-exit --exit-code-from web

- name: Cleanup
if: always()
run: |
docker compose -f docker-compose-tests.yml down
run: docker compose -f docker-compose-tests.yml down -v
68 changes: 36 additions & 32 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
FROM python:3.10

# Setting environment variables for Python
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PIP_NO_CACHE_DIR=off
ENV ALEMBIC_CONFIG=/usr/src/alembic/alembic.ini

# Installing dependencies
RUN apt update && apt install -y \
FROM python:3.10-slim

# ---------------------------------------------
# ENV
# ---------------------------------------------
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=off \
ALEMBIC_CONFIG=/usr/src/alembic/alembic.ini \
POETRY_VIRTUALENVS_CREATE=false \
POETRY_HOME="/opt/poetry"

# ---------------------------------------------
# System dependencies
# ---------------------------------------------
RUN apt update && apt install -y --no-install-recommends \
gcc \
libpq-dev \
netcat-openbsd \
postgresql-client \
dos2unix \
&& apt clean
&& apt clean && rm -rf /var/lib/apt/lists/*

# Install Poetry
RUN python -m pip install --upgrade pip && \
pip install poetry
# ---------------------------------------------
# Install Poetry
# ---------------------------------------------
RUN pip install --no-cache-dir poetry

# Copy dependency files
COPY ./poetry.lock /usr/src/poetry/poetry.lock
COPY ./pyproject.toml /usr/src/poetry/pyproject.toml
# ---------------------------------------------
# Copy dependencies files
# ---------------------------------------------
COPY ./poetry.lock ./pyproject.toml /usr/src/poetry/
COPY ./alembic.ini /usr/src/alembic/alembic.ini

# Configure Poetry to avoid creating a virtual environment
RUN poetry config virtualenvs.create false

# Selecting a working directory
WORKDIR /usr/src/poetry

# Install dependencies with Poetry
RUN poetry lock
# ---------------------------------------------
# Install python dependencies
# ---------------------------------------------
RUN poetry install --no-root --only main

# Selecting a working directory
# ---------------------------------------------
# Copy app source code
# ---------------------------------------------
WORKDIR /usr/src/fastapi

# Copy the source code
COPY ./src .

# Copy commands
# ---------------------------------------------
# Copy shell scripts
# ---------------------------------------------
COPY ./commands /commands
RUN dos2unix /commands/*.sh && chmod +x /commands/*.sh

# Ensure Unix-style line endings for scripts
RUN dos2unix /commands/*.sh

# Add execute bit to commands files
RUN chmod +x /commands/*.sh
EXPOSE 8000
27 changes: 27 additions & 0 deletions Fastapi-Key/fastapi-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtRoYbbAf/vEy5q8HnaDHHf1542pd/tWN7cLj4znarMuEUDId
DTaNGqNdmuA5iRo9nQXcUYuTSEHSPVPE4YVakQuhnUixkjUpY1UeLELf9HeZUuSx
a78vSd/KV8mY9TMwVFVxe4bJtvY5ecl0Z1TaWIXdeQjgJM6VGWphFonAOn/KXzil
vJYWqdAB7fsizbvOIyGVg968/kTuVVBqmLcP6nD7PF27WyfVaX0S6BW2mvyvUv5r
xP8oBziXn6deoVoB/FeoIAtc+hOsGbH+HRNF7f0bk8Sy0ICDxnlKuzZOGF3pJxtG
Od4GGoCCXw5ybMUgFAS5u6x7aLAJUtu08UZlDQIDAQABAoIBAB3WtNutvzfUKdzU
Td7s1KHeV6zeo6oPKZlZGQ8hQyRhS+WivCKnMEyJSizwcMpkJm6uvoQ5CQVIwwkT
ukamICvxzKkLhyZd1/nj1lrYkenHot2DjLACChqT4tTgBHGyA26BvXbq2GVq4EFu
rd0wn+Eh/UGp8rRdzTcozeJWw/NfP6aKoSy5oXaAOkLMFr2Xtzhf9bxd2toEUMNV
WaoztnGM7iyjiKO346Xs2spXyPL8g7ruKvhelLqZcriyfmZojWDXFieUQfKh7RGM
fmdRhpjpixBcrnYFWoMgHGD+YVJWqfq2W6YIEhC4F7nlkrd8lWgTce/77lBGz/ZZ
y3i00YkCgYEA54+St27GZyaH8fsDgUuv14sIsfZPjh9WN4kWQ17wNbOg0HdANGYn
HJsqFbnc+wrjaNAXJEMjQQkX1JC5Z5XGu3kcjGhig80a91w9MjgKb+j4R5klmJfn
fSRYqn+qR3/I+9yydqNy6nmu5tbDijcq83w5pvH63Dvkynx27vzT3g8CgYEAyDcy
z6QW8pqF09k2R1R4DYqaYT7brxZVlrSQVQpT8pp/L85qdA5DW049usT9z6Yo9AU0
nhOUX2bdu0dTdQI2M9hU+VMsxXoSvp1174I4ief/L6MAnjYKq3zwOcmWf0z0CUfv
jyPF0BNTO5wU7tKRAG7OM9m5P6yBpl3qNOWZZyMCgYAHjA5kHCArTH4xzNSYajcY
91cZNcXyA+Nze+xV1cpOdcRQBxL4oju0GvqMKrqhFAHFcMLQU7/aZsl4DB9+2MrZ
YWEk0mIjmZO/bCoycNEmRJmS+9LV3l2VHpIlPHP4UJnkW5UVeM5OBhlFPMqyvomP
1gSrBEgA1ZLnZpcZwvPE0QKBgQCDxkC9BSTNXAKE4kulOuUhFH5zTqnMXQjL/d+Y
+QkzfZgEYPkTlWejqqVaTdk3WAdQRDTTbdOTzuJVJqa5NlFyYgnbZ3DqMGQx7PT4
RF7t9TGxqrj3aF4xbDO39QQGmSFtElmLfmHthyqa6ymj3hbtkABLhBs221UhqGJD
mnISvQKBgQCuyMxrFuY74+AEcywdasHuzUSEUrbL9ir79ry18zYfxwSAo7jeqZU6
q+VzWurD4Y+mILLp+R9qU5BdL7VFrF+uhnNTARGJXc+QIV6qdDbuMAuDi+51sTpE
2szdV1wHbjVuACEi1QZScaPb842zdQTPq1F2L4oaW9jZW9+xN7g83Q==
-----END RSA PRIVATE KEY-----
4 changes: 2 additions & 2 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -1442,11 +1442,11 @@ Adding your user to the `docker` group allows you to run Docker commands without
4. **Create `.env` Files:**
- **Main Project Directory:**
```shell
cp .env.sample .env
cp .env .env
```
- **Nginx Configuration Directory:**
```shell
cp docker/nginx/.env.sample docker/nginx/.env
cp docker/nginx/.env docker/nginx/.env
```
**Comment:** Copies sample environment files to actual `.env` files. Update these files with your configuration details, including login credentials for Nginx documentation authentication.

Expand Down
Binary file added requirements.txt
Binary file not shown.
23 changes: 19 additions & 4 deletions src/config/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import os

from dotenv import load_dotenv
from fastapi import Depends

from config.settings import TestingSettings, Settings, BaseAppSettings
from notifications import EmailSenderInterface, EmailSender
from config.settings import (
TestingSettings,
Settings,
BaseAppSettings,
)
from notifications import (
EmailSenderInterface,
EmailSender,
)
from security.interfaces import JWTAuthManagerInterface
from security.token_manager import JWTAuthManager
from storages import S3StorageInterface, S3StorageClient
from storages import (
S3StorageInterface,
S3StorageClient,
)


load_dotenv()

print(os.getenv("SECRET_KEY_ACCESS"))


def get_settings() -> BaseAppSettings:
Expand Down
4 changes: 2 additions & 2 deletions src/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class Settings(BaseAppSettings):
POSTGRES_DB_PORT: int = int(os.getenv("POSTGRES_DB_PORT", 5432))
POSTGRES_DB: str = os.getenv("POSTGRES_DB", "test_db")

SECRET_KEY_ACCESS: str = os.getenv("SECRET_KEY_ACCESS", os.urandom(32))
SECRET_KEY_REFRESH: str = os.getenv("SECRET_KEY_REFRESH", os.urandom(32))
SECRET_KEY_ACCESS: str = os.getenv("SECRET_KEY_ACCESS") or os.urandom(32).hex()
SECRET_KEY_REFRESH: str = os.getenv("SECRET_KEY_REFRESH") or os.urandom(32).hex()
JWT_SIGNING_ALGORITHM: str = os.getenv("JWT_SIGNING_ALGORITHM", "HS256")


Expand Down
31 changes: 21 additions & 10 deletions src/database/models/accounts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import enum
from datetime import datetime, date, timedelta, timezone
from typing import List, Optional
from datetime import (
datetime,
date,
timedelta,
timezone,
)
from typing import (
List,
Optional,
)

from sqlalchemy import (
ForeignKey,
Expand All @@ -23,7 +31,10 @@

from database import Base
from database.validators import accounts as validators
from security.passwords import hash_password, verify_password
from security.passwords import (
hash_password,
verify_password,
)
from security.utils import generate_secure_token


Expand All @@ -46,7 +57,7 @@ class UserGroupModel(Base):

users: Mapped[List["UserModel"]] = relationship("UserModel", back_populates="group")

def __repr__(self):
def __repr__(self) -> str:
return f"<UserGroupModel(id={self.id}, name={self.name})>"


Expand Down Expand Up @@ -91,7 +102,7 @@ class UserModel(Base):
cascade="all, delete-orphan"
)

def __repr__(self):
def __repr__(self) -> str:
return f"<UserModel(id={self.id}, email={self.email}, is_active={self.is_active})>"

def has_group(self, group_name: UserGroupEnum) -> bool:
Expand Down Expand Up @@ -128,7 +139,7 @@ def verify_password(self, raw_password: str) -> bool:
return verify_password(raw_password, self._hashed_password)

@validates("email")
def validate_email(self, key, value):
def validate_email(self, key, value) -> Optional[str]:
return validators.validate_email(value.lower())


Expand All @@ -151,7 +162,7 @@ class UserProfileModel(Base):

__table_args__ = (UniqueConstraint("user_id"),)

def __repr__(self):
def __repr__(self) -> str:
return (
f"<UserProfileModel(id={self.id}, first_name={self.first_name}, last_name={self.last_name}, "
f"gender={self.gender}, date_of_birth={self.date_of_birth})>"
Expand Down Expand Up @@ -184,7 +195,7 @@ class ActivationTokenModel(TokenBaseModel):

__table_args__ = (UniqueConstraint("user_id"),)

def __repr__(self):
def __repr__(self) -> str:
return f"<ActivationTokenModel(id={self.id}, token={self.token}, expires_at={self.expires_at})>"


Expand All @@ -195,7 +206,7 @@ class PasswordResetTokenModel(TokenBaseModel):

__table_args__ = (UniqueConstraint("user_id"),)

def __repr__(self):
def __repr__(self) -> str:
return f"<PasswordResetTokenModel(id={self.id}, token={self.token}, expires_at={self.expires_at})>"


Expand All @@ -222,5 +233,5 @@ def create(cls, user_id: int | Mapped[int], days_valid: int, token: str) -> "Ref
expires_at = datetime.now(timezone.utc) + timedelta(days=days_valid)
return cls(user_id=user_id, expires_at=expires_at, token=token)

def __repr__(self):
def __repr__(self) -> str:
return f"<RefreshTokenModel(id={self.id}, token={self.token}, expires_at={self.expires_at})>"
2 changes: 1 addition & 1 deletion src/database/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

class Base(DeclarativeBase):
@classmethod
def default_order_by(cls):
def default_order_by(cls) -> None:
return None
Loading