Skip to content

Commit f898c2b

Browse files
committed
UPDATE
1 parent 9d5c42f commit f898c2b

File tree

9 files changed

+623
-4
lines changed

9 files changed

+623
-4
lines changed

backend/app/__init__.py

Whitespace-only changes.

backend/app/core/__init__.py

Whitespace-only changes.

backend/app/core/config.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from pydantic_settings import BaseSettings, SettingsConfigDict
2+
from typing import List
3+
from loguru import logger
4+
import sys
5+
6+
7+
class Settings(BaseSettings):
8+
9+
API_VERSION: str = "v1"
10+
API_V1_STR: str = f"/api/{API_VERSION}"
11+
12+
DB_HOST: str
13+
DB_PORT: str
14+
DB_NAME: str
15+
DB_PASS: str
16+
DB_USER: str
17+
18+
@property
19+
def SQLALCHEMY_DATABASE_URI(self) -> str:
20+
return f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
21+
22+
class Config:
23+
env_file = "../.env"
24+
25+
class LogConfig:
26+
LOGGING_LEVEL = "DEBUG"
27+
LOGGING_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | <level>{message}</level>"
28+
29+
@staticmethod
30+
def configure_logging():
31+
logger.remove()
32+
33+
logger.add(
34+
sys.stderr, format=LogConfig.LOGGING_FORMAT, level=LogConfig.LOGGING_LEVEL
35+
)
36+
37+
38+
LogConfig.configure_logging()
39+
40+
settings = Settings()

backend/app/core/db.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from sqlmodel import SQLModel
2+
from sqlalchemy.ext.asyncio import create_async_engine
3+
from app.core.config import settings
4+
import asyncpg
5+
import psycopg2
6+
from loguru import logger
7+
8+
engine = create_async_engine(str(settings.SQLALCHEMY_DATABASE_URI), echo=True)
9+
10+
async def create_extension():
11+
conn: asyncpg.Connection = await asyncpg.connect(
12+
user=settings.DB_USER,
13+
password=settings.DB_PASS,
14+
database=settings.DB_NAME,
15+
host=settings.DB_HOST,
16+
)
17+
try:
18+
await conn.execute("CREATE EXTENSION IF NOT EXISTS vector")
19+
logger.info("pgvector extension created or already exists.")
20+
except Exception as e:
21+
logger.error(f"Error creating pgvector extension: {e}")
22+
finally:
23+
await conn.close()
24+
25+
26+
def create_database(database_name, user, password, host, port):
27+
try:
28+
# Connect to the default database
29+
conn = psycopg2.connect(
30+
dbname=database_name, user=user, password=password, host=host, port=port
31+
)
32+
conn.autocommit = True
33+
cur = conn.cursor()
34+
35+
# Check if database exists
36+
cur.execute(
37+
f"SELECT 1 FROM pg_catalog.pg_database WHERE datname = '{database_name}'"
38+
)
39+
exists = cur.fetchone()
40+
if not exists:
41+
42+
cur.execute(f"CREATE DATABASE {database_name}")
43+
logger.info(f"Database '{database_name}' created.")
44+
else:
45+
logger.info(f"Database '{database_name}' already exists.")
46+
47+
cur.close()
48+
conn.close()
49+
except Exception as e:
50+
logger.error(f"Error creating database: {e}")
51+
52+
async def init_db() -> None:
53+
create_database(
54+
settings.DB_NAME,
55+
settings.DB_USER,
56+
settings.DB_PASS,
57+
settings.DB_HOST,
58+
settings.DB_PORT,
59+
)
60+
async with engine.begin() as conn:
61+
# Use run_sync to execute the create_all method in an asynchronous context
62+
await conn.run_sync(SQLModel.metadata.create_all)
63+
64+
# Your existing database initialization logic here
65+
# For example, creating extensions or setting up initial data
66+
await create_extension()
67+
logger.info("Database initialized and all tables created if they didn't exist.")

backend/app/main.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
12
from fastapi import FastAPI
23
from fastapi.responses import RedirectResponse
34
from contextlib import asynccontextmanager
45
from fastapi.middleware.cors import CORSMiddleware
6+
from app.core.db import init_db
57

68

9+
@asynccontextmanager
10+
async def app_lifespan(app: FastAPI):
11+
await init_db()
12+
yield
713

8-
app = FastAPI()
914

15+
app = FastAPI(lifespan=app_lifespan)
1016

1117
app.add_middleware(
1218
CORSMiddleware,
@@ -16,13 +22,13 @@
1622
allow_headers=["*"],
1723
)
1824

19-
2025
@app.get("/metrics")
2126
def metrics():
2227
return {"message": "Metrics endpoint"}
2328

24-
2529
@app.get("/")
2630
async def home():
2731
return {"data": "Hello"}
2832

33+
34+

backend/app/models/__init__.py

Whitespace-only changes.

backend/app/models/user_model.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from sqlmodel import Field, Relationship, SQLModel
2+
3+
4+
# Shared properties
5+
# TODO replace email str with EmailStr when sqlmodel supports it
6+
class UserBase(SQLModel):
7+
email: str = Field(unique=True, index=True)
8+
is_active: bool = True
9+
is_superuser: bool = False
10+
full_name: str | None = None
11+
12+
13+
# Properties to receive via API on creation
14+
class UserCreate(UserBase):
15+
password: str
16+
17+
18+
# TODO replace email str with EmailStr when sqlmodel supports it
19+
class UserCreateOpen(SQLModel):
20+
email: str
21+
password: str
22+
full_name: str | None = None
23+
24+
25+
# Properties to receive via API on update, all are optional
26+
# TODO replace email str with EmailStr when sqlmodel supports it
27+
class UserUpdate(UserBase):
28+
email: str | None = None # type: ignore
29+
password: str | None = None
30+
31+
32+
# TODO replace email str with EmailStr when sqlmodel supports it
33+
class UserUpdateMe(SQLModel):
34+
full_name: str | None = None
35+
email: str | None = None
36+
37+
38+
class UpdatePassword(SQLModel):
39+
current_password: str
40+
new_password: str
41+
42+
43+
# Database model, database table inferred from class name
44+
class User(UserBase, table=True):
45+
id: int | None = Field(default=None, primary_key=True)
46+
hashed_password: str
47+
items: list["Item"] = Relationship(back_populates="owner")
48+
49+
50+
# Properties to return via API, id is always required
51+
class UserOut(UserBase):
52+
id: int
53+
54+
55+
class UsersOut(SQLModel):
56+
data: list[UserOut]
57+
count: int
58+
59+
60+
# Shared properties
61+
class ItemBase(SQLModel):
62+
title: str
63+
description: str | None = None
64+
65+
66+
# Properties to receive on item creation
67+
class ItemCreate(ItemBase):
68+
title: str
69+
70+
71+
# Properties to receive on item update
72+
class ItemUpdate(ItemBase):
73+
title: str | None = None # type: ignore
74+
75+
76+
# Database model, database table inferred from class name
77+
class Item(ItemBase, table=True):
78+
id: int | None = Field(default=None, primary_key=True)
79+
title: str
80+
owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False)
81+
owner: User | None = Relationship(back_populates="items")
82+
83+
84+
# Properties to return via API, id is always required
85+
class ItemOut(ItemBase):
86+
id: int
87+
owner_id: int
88+
89+
90+
class ItemsOut(SQLModel):
91+
data: list[ItemOut]
92+
count: int
93+
94+
95+
# Generic message
96+
class Message(SQLModel):
97+
message: str
98+
99+
100+
# JSON payload containing access token
101+
class Token(SQLModel):
102+
access_token: str
103+
token_type: str = "bearer"
104+
105+
106+
# Contents of JWT token
107+
class TokenPayload(SQLModel):
108+
sub: int | None = None
109+
110+
111+
class NewPassword(SQLModel):
112+
token: str
113+
new_password: str

0 commit comments

Comments
 (0)