Skip to content

Commit 1329444

Browse files
committed
Add lots of changes and docker build
1 parent f9af9ab commit 1329444

27 files changed

+430
-90
lines changed

.env

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
DB_URL="postgresql+psycopg2://postgres:1234@localhost:5432"
1+
POSTGRES_USER=postgres
2+
POSTGRES_PASSWORD=1234
3+
POSTGRES_DB=postgres

Dockerfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.12
2+
3+
ENV PYTHONDONTWRITEBYCODE = 1
4+
ENV PYTHONUUNBUFFERED = 1
5+
6+
WORKDIR /app
7+
8+
COPY requirements.txt /app/
9+
RUN pip install -r requirements.txt
10+
COPY . /app/

Makefile

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
run:
2-
fastapi dev main.py
3-
2+
uvicorn main:app --reload
3+
44
review:
55
alembic revision --autogenerate -m "Create User model"
66

77
upgrade:
8-
alembic upgrade head
8+
alembic upgrade head
9+
10+
downgrade:
11+
alembic downgrade base
12+
13+
generate_migrations: upgrade
14+
alembic revision --autogenerate -m "Create User and Note models"

__pycache__/main.cpython-312.pyc

-160 Bytes
Binary file not shown.

alembic.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
6060
# are written from script.py.mako
6161
# output_encoding = utf-8
6262

63-
sqlalchemy.url = postgresql://postgres:1234@localhost/postgres
63+
sqlalchemy.url = postgresql://postgres:1234@notesdb/postgres
6464

6565

6666
[post_write_hooks]

config/database.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from sqlalchemy import create_engine
2+
from sqlalchemy.orm import sessionmaker, Session
3+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
4+
5+
DB_USER = "postgres"
6+
DB_PASSWORD = "1234"
7+
DB_HOST = "notesdb"
8+
DB_NAME = "postgres"
9+
10+
DB_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:5432/{DB_NAME}"
11+
12+
# engine = create_engine(DB_URL)
13+
engine = create_async_engine(DB_URL)
14+
15+
# SessionLocal = sessionmaker(engine, autoflush=False, autocommit=False)
16+
SessionLocal = async_sessionmaker(bind=engine, autoflush=False)

database/database.py

-8
This file was deleted.

docker-compose.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
services:
2+
web:
3+
container_name: notes_api
4+
build: .
5+
command: bash -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000"
6+
depends_on:
7+
db:
8+
condition: service_healthy
9+
ports:
10+
- 8000:8000
11+
12+
db:
13+
container_name: notesdb
14+
image: postgres:latest
15+
restart: always
16+
env_file:
17+
- .env
18+
ports:
19+
- 5432:5432
20+
volumes:
21+
- pgdata://var/lib/postgresql/data
22+
healthcheck:
23+
test: ["CMD-SHELL", "pg_isready -U postgres"]
24+
interval: 5s
25+
timeout: 5s
26+
retries: 5
27+
28+
volumes:
29+
pgdata:

helpers/helpers.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class NotFoundError(Exception):
2+
pass

main.py

+5-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
from fastapi import FastAPI
2-
from sqlalchemy.orm import session
32

4-
from database.database import engine, new_session
5-
from routers.routes import router
3+
from router.note_routes import router as notes_router
4+
from router.auth_routes import router as auth_router
65

76

8-
app = FastAPI(title="Trading App", version="1.0")
9-
app.include_router(router)
10-
11-
if __name__ == "__main__":
12-
import uvicorn
13-
14-
uvicorn.run(app, host="0.0.0.0", port=8000)
7+
app = FastAPI(title="Notes Service", version="1.0")
8+
app.include_router(auth_router)
9+
app.include_router(notes_router)
File renamed without changes.

migrations/env.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from sqlalchemy import pool
55

66
from alembic import context
7-
from models.user import Base
7+
from models.user_models import Base as UserBase
8+
from models.notes_models import Base as NotesBase
9+
810

911
# this is the Alembic Config object, which provides
1012
# access to the values within the .ini file in use.
@@ -19,7 +21,7 @@
1921
# for 'autogenerate' support
2022
# from myapp import mymodel
2123
# target_metadata = mymodel.Base.metadata
22-
target_metadata = Base.metadata
24+
target_metadata = [UserBase.metadata, NotesBase.metadata]
2325

2426
# other values from the config, defined by the needs of env.py,
2527
# can be acquired:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Create User and Note models
2+
3+
Revision ID: 6cd2eb75776c
4+
Revises: 7ae8b93ae7b0
5+
Create Date: 2024-09-11 10:21:00.142559
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '6cd2eb75776c'
16+
down_revision: Union[str, None] = '7ae8b93ae7b0'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.add_column('users', sa.Column('password_hash', sa.String(), nullable=False))
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade() -> None:
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column('users', 'password_hash')
30+
# ### end Alembic commands ###

migrations/versions/6b78fdfedaa6_create_user_model.py renamed to migrations/versions/7ae8b93ae7b0_create_user_and_note_models.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
"""Create User model
1+
"""Create User and Note models
22
3-
Revision ID: 6b78fdfedaa6
3+
Revision ID: 7ae8b93ae7b0
44
Revises:
5-
Create Date: 2024-09-09 11:55:24.008408
5+
Create Date: 2024-09-10 17:24:21.056484
66
77
"""
88
from typing import Sequence, Union
@@ -12,7 +12,7 @@
1212

1313

1414
# revision identifiers, used by Alembic.
15-
revision: str = '6b78fdfedaa6'
15+
revision: str = '7ae8b93ae7b0'
1616
down_revision: Union[str, None] = None
1717
branch_labels: Union[str, Sequence[str], None] = None
1818
depends_on: Union[str, Sequence[str], None] = None
@@ -26,10 +26,20 @@ def upgrade() -> None:
2626
sa.PrimaryKeyConstraint('id'),
2727
sa.UniqueConstraint('name')
2828
)
29+
op.create_table('notes',
30+
sa.Column('id', sa.Integer(), nullable=False),
31+
sa.Column('content', sa.Text(), nullable=False),
32+
sa.Column('user_id', sa.Integer(), nullable=False),
33+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
34+
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
35+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
36+
sa.PrimaryKeyConstraint('id')
37+
)
2938
# ### end Alembic commands ###
3039

3140

3241
def downgrade() -> None:
3342
# ### commands auto generated by Alembic - please adjust! ###
43+
op.drop_table('notes')
3444
op.drop_table('users')
3545
# ### end Alembic commands ###

models/__init__.py

Whitespace-only changes.

models/notes_models.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, func
2+
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, relationship
3+
4+
from datetime import datetime
5+
from models.user_models import UserModel
6+
7+
8+
class Base(DeclarativeBase):
9+
pass
10+
11+
12+
class NoteModel(Base):
13+
__tablename__ = "notes"
14+
15+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
16+
content: Mapped[str] = mapped_column(Text, nullable=False)
17+
user_id: Mapped[int] = mapped_column(ForeignKey(UserModel.id))
18+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
19+
updated_at: Mapped[datetime] = mapped_column(
20+
DateTime, server_default=func.now(), onupdate=func.now()
21+
)
22+
23+
def __repr__(self):
24+
return f"id: {self.id}, content: {self.content}"

models/user.py renamed to models/user_models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from sqlalchemy import Column, String, Integer
1+
from sqlalchemy import String, Integer
22
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
33

44

@@ -11,6 +11,7 @@ class UserModel(Base):
1111

1212
id: Mapped[int] = mapped_column(Integer, primary_key=True)
1313
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
14+
password_hash: Mapped[str] = mapped_column(String)
1415

1516
def __repr__(self):
1617
return f"id: {self.id}, name: {self.name}"

repository.py

-28
This file was deleted.

repository/auth_repository.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from fastapi import Depends
2+
from jose import jwt, JWTError
3+
from sqlalchemy.future import select
4+
5+
from datetime import datetime, timedelta
6+
from typing import Annotated
7+
8+
from models.user_models import UserModel
9+
from schemas.user_schemas import (
10+
CreateUserInput,
11+
bcrypt_context,
12+
SECRET_KEY,
13+
ALGORITHM,
14+
oauth2_bearer,
15+
)
16+
from config.database import SessionLocal
17+
18+
19+
class AuthRepository:
20+
@classmethod
21+
async def create_user(cls, data: CreateUserInput) -> int:
22+
async with SessionLocal() as session:
23+
user = UserModel(
24+
name=data.name, password_hash=bcrypt_context.hash(data.password)
25+
)
26+
session.add(user)
27+
await session.commit()
28+
await session.refresh(user)
29+
return user.id
30+
31+
@classmethod
32+
async def authenticate_user(cls, name: str, password: str):
33+
async with SessionLocal() as session:
34+
query = select(UserModel).where(UserModel.name == name)
35+
result = await session.execute(query)
36+
user = result.scalars().one()
37+
if not user:
38+
return False
39+
if not bcrypt_context.verify(password, user.password_hash):
40+
return False
41+
return user
42+
43+
@classmethod
44+
def create_access_token(cls, name: str, id: int, ttl: timedelta):
45+
data_to_encode = {"sub": name, "id": id}
46+
expires = datetime.now() + ttl
47+
data_to_encode.update({"exp": expires})
48+
return jwt.encode(data_to_encode, SECRET_KEY, algorithm=ALGORITHM)
49+
50+
@classmethod
51+
def parse_access_token(cls, token: Annotated[str, Depends(oauth2_bearer)]):
52+
try:
53+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
54+
username: str = payload.get("sub")
55+
user_id: str = payload.get("id")
56+
if username is None or user_id is None:
57+
return False
58+
return {"username": username, "user_id": user_id}
59+
except JWTError:
60+
return False

0 commit comments

Comments
 (0)