diff --git a/backend/app/api/routes/login.py b/backend/app/api/routes/login.py
index 980c66f..964f5fe 100644
--- a/backend/app/api/routes/login.py
+++ b/backend/app/api/routes/login.py
@@ -11,7 +11,7 @@ from app.core import security
from app.core.config import settings
from app.core.security import get_password_hash
from app.models import Message, NewPassword, Token, UserPublic
-from app.utils import (
+from app.utils.common import (
generate_password_reset_token,
generate_reset_password_email,
send_email,
diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py
index 6429818..71fd85d 100644
--- a/backend/app/api/routes/users.py
+++ b/backend/app/api/routes/users.py
@@ -24,7 +24,7 @@ from app.models import (
UserUpdate,
UserUpdateMe,
)
-from app.utils import generate_new_account_email, send_email
+from app.utils.common import generate_new_account_email, send_email
router = APIRouter(prefix="/users", tags=["users"])
diff --git a/backend/app/api/routes/utils.py b/backend/app/api/routes/utils.py
index fc09341..9977c03 100644
--- a/backend/app/api/routes/utils.py
+++ b/backend/app/api/routes/utils.py
@@ -3,7 +3,7 @@ from pydantic.networks import EmailStr
from app.api.deps import get_current_active_superuser
from app.models import Message
-from app.utils import generate_test_email, send_email
+from app.utils.common import generate_test_email, send_email
router = APIRouter(prefix="/utils", tags=["utils"])
diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py
deleted file mode 100644
index 7b6461d..0000000
--- a/backend/app/api/v1/api.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from fastapi import APIRouter
-
-from app.api.v1.endpoints import auth, users, utils, todos
-
-api_router = APIRouter()
-api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
-api_router.include_router(users.router, prefix="/users", tags=["users"])
-api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
-api_router.include_router(todos.router, prefix="/todos", tags=["todos"])
\ No newline at end of file
diff --git a/backend/app/api/v1/endpoints/todos.py b/backend/app/api/v1/endpoints/todos.py
deleted file mode 100644
index 01423ff..0000000
--- a/backend/app/api/v1/endpoints/todos.py
+++ /dev/null
@@ -1,75 +0,0 @@
-from typing import List
-from fastapi import APIRouter, Depends, HTTPException
-from sqlalchemy.orm import Session
-
-from app.api import deps
-from app.schemas.todo import Todo, TodoCreate, TodoUpdate
-from app.models.todo import Todo as TodoModel
-from app.crud.todo import todo
-
-router = APIRouter()
-
-@router.get("/", response_model=List[Todo])
-def read_todos(
- db: Session = Depends(deps.get_db),
- skip: int = 0,
- limit: int = 100,
-):
- """
- Retrieve todos.
- """
- return todo.get_multi(db, skip=skip, limit=limit)
-
-@router.post("/", response_model=Todo)
-def create_todo(
- *,
- db: Session = Depends(deps.get_db),
- todo_in: TodoCreate,
-):
- """
- Create new todo.
- """
- return todo.create(db=db, obj_in=todo_in)
-
-@router.put("/{id}", response_model=Todo)
-def update_todo(
- *,
- db: Session = Depends(deps.get_db),
- id: int,
- todo_in: TodoUpdate,
-):
- """
- Update a todo.
- """
- todo_current = todo.get(db=db, id=id)
- if not todo_current:
- raise HTTPException(status_code=404, detail="Todo not found")
- return todo.update(db=db, db_obj=todo_current, obj_in=todo_in)
-
-@router.get("/{id}", response_model=Todo)
-def read_todo(
- *,
- db: Session = Depends(deps.get_db),
- id: int,
-):
- """
- Get todo by ID.
- """
- todo_current = todo.get(db=db, id=id)
- if not todo_current:
- raise HTTPException(status_code=404, detail="Todo not found")
- return todo_current
-
-@router.delete("/{id}", response_model=Todo)
-def delete_todo(
- *,
- db: Session = Depends(deps.get_db),
- id: int,
-):
- """
- Delete todo.
- """
- todo_current = todo.get(db=db, id=id)
- if not todo_current:
- raise HTTPException(status_code=404, detail="Todo not found")
- return todo.remove(db=db, id=id)
\ No newline at end of file
diff --git a/backend/app/backend_pre_start.py b/backend/app/core/backend_pre_start.py
similarity index 100%
rename from backend/app/backend_pre_start.py
rename to backend/app/core/backend_pre_start.py
diff --git a/backend/app/initial_data.py b/backend/app/core/initial_data.py
similarity index 100%
rename from backend/app/initial_data.py
rename to backend/app/core/initial_data.py
diff --git a/backend/app/tests_pre_start.py b/backend/app/core/tests_pre_start.py
similarity index 100%
rename from backend/app/tests_pre_start.py
rename to backend/app/core/tests_pre_start.py
diff --git a/backend/app/crud/todo.py b/backend/app/crud/todo.py
deleted file mode 100644
index 76ef7d1..0000000
--- a/backend/app/crud/todo.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from typing import List
-from fastapi.encoders import jsonable_encoder
-from sqlalchemy.orm import Session
-
-from app.crud.base import CRUDBase
-from app.models.todo import Todo
-from app.schemas.todo import TodoCreate, TodoUpdate
-
-class CRUDTodo(CRUDBase[Todo, TodoCreate, TodoUpdate]):
- def create(self, db: Session, *, obj_in: TodoCreate) -> Todo:
- obj_in_data = jsonable_encoder(obj_in)
- db_obj = self.model(**obj_in_data)
- db.add(db_obj)
- db.commit()
- db.refresh(db_obj)
- return db_obj
-
- def get_multi(
- self, db: Session, *, skip: int = 0, limit: int = 100
- ) -> List[Todo]:
- return db.query(self.model).offset(skip).limit(limit).all()
-
-todo = CRUDTodo(Todo)
\ No newline at end of file
diff --git a/backend/app/email-templates/build/new_account.html b/backend/app/email-templates/build/new_account.html
deleted file mode 100644
index 3445050..0000000
--- a/backend/app/email-templates/build/new_account.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
{{ project_name }} - New Account | Welcome to your new account! | Here are your account details: | Username: {{ username }} | Password: {{ password }} | | |
|
\ No newline at end of file
diff --git a/backend/app/email-templates/build/reset_password.html b/backend/app/email-templates/build/reset_password.html
deleted file mode 100644
index 4148a5b..0000000
--- a/backend/app/email-templates/build/reset_password.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{{ project_name }} - Password Recovery | Hello {{ username }} | We've received a request to reset your password. You can do it by clicking the button below: | | Or copy and paste the following link into your browser: | | This password will expire in {{ valid_hours }} hours. | | If you didn't request a password recovery you can disregard this email. |
|
\ No newline at end of file
diff --git a/backend/app/email-templates/build/test_email.html b/backend/app/email-templates/build/test_email.html
deleted file mode 100644
index 04d0d85..0000000
--- a/backend/app/email-templates/build/test_email.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{{ project_name }} | Test email for: {{ email }} | |
|
\ No newline at end of file
diff --git a/backend/app/email-templates/src/new_account.mjml b/backend/app/email-templates/src/new_account.mjml
deleted file mode 100644
index f41a3e3..0000000
--- a/backend/app/email-templates/src/new_account.mjml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- {{ project_name }} - New Account
- Welcome to your new account!
- Here are your account details:
- Username: {{ username }}
- Password: {{ password }}
- Go to Dashboard
-
-
-
-
-
diff --git a/backend/app/email-templates/src/reset_password.mjml b/backend/app/email-templates/src/reset_password.mjml
deleted file mode 100644
index 743f5d7..0000000
--- a/backend/app/email-templates/src/reset_password.mjml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- {{ project_name }} - Password Recovery
- Hello {{ username }}
- We've received a request to reset your password. You can do it by clicking the button below:
- Reset password
- Or copy and paste the following link into your browser:
- {{ link }}
- This password will expire in {{ valid_hours }} hours.
-
- If you didn't request a password recovery you can disregard this email.
-
-
-
-
diff --git a/backend/app/email-templates/src/test_email.mjml b/backend/app/email-templates/src/test_email.mjml
deleted file mode 100644
index 45d58d6..0000000
--- a/backend/app/email-templates/src/test_email.mjml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- {{ project_name }}
- Test email for: {{ email }}
-
-
-
-
-
diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/backend/app/models/__init__.py
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/backend/app/models/auth.py b/backend/app/models/auth.py
new file mode 100644
index 0000000..0f12523
--- /dev/null
+++ b/backend/app/models/auth.py
@@ -0,0 +1,17 @@
+from sqlmodel import SQLModel, Field
+
+
+# JSON payload containing access token
+class Token(SQLModel):
+ access_token: str
+ token_type: str = "bearer"
+
+
+# Contents of JWT token
+class TokenPayload(SQLModel):
+ sub: str | None = None
+
+
+class NewPassword(SQLModel):
+ token: str
+ new_password: str = Field(min_length=8, max_length=40)
\ No newline at end of file
diff --git a/backend/app/models/item.py b/backend/app/models/item.py
new file mode 100644
index 0000000..68a55d0
--- /dev/null
+++ b/backend/app/models/item.py
@@ -0,0 +1,40 @@
+import uuid
+from sqlmodel import Field, Relationship, SQLModel
+
+from app.models.user import User
+
+
+# Shared properties
+class ItemBase(SQLModel):
+ title: str = Field(min_length=1, max_length=255)
+ description: str | None = Field(default=None, max_length=255)
+
+
+# Properties to receive on item creation
+class ItemCreate(ItemBase):
+ pass
+
+
+# Properties to receive on item update
+class ItemUpdate(ItemBase):
+ title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
+
+
+# Database model, database table inferred from class name
+class Item(ItemBase, table=True):
+ id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
+ owner_id: uuid.UUID = Field(
+ foreign_key="user.id", nullable=False, ondelete="CASCADE"
+ )
+ owner: User | None = Relationship(back_populates="items")
+
+
+# Properties to return via API, id is always required
+class ItemPublic(ItemBase):
+ id: uuid.UUID
+ owner_id: uuid.UUID
+
+
+class ItemsPublic(SQLModel):
+ data: list[ItemPublic]
+ count: int
\ No newline at end of file
diff --git a/backend/app/models/todo.py b/backend/app/models/todo.py
deleted file mode 100644
index 6e53672..0000000
--- a/backend/app/models/todo.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from sqlalchemy import Column, Integer, String, Boolean, DateTime
-from sqlalchemy.sql import func
-from app.db.base_class import Base
-
-class Todo(Base):
- __tablename__ = "todos"
-
- id = Column(Integer, primary_key=True, index=True)
- title = Column(String, index=True)
- description = Column(String, nullable=True)
- completed = Column(Boolean, default=False)
- created_at = Column(DateTime(timezone=True), server_default=func.now())
- updated_at = Column(DateTime(timezone=True), onupdate=func.now())
\ No newline at end of file
diff --git a/backend/app/models.py b/backend/app/models/user.py
similarity index 52%
rename from backend/app/models.py
rename to backend/app/models/user.py
index 2389b4a..33b1412 100644
--- a/backend/app/models.py
+++ b/backend/app/models/user.py
@@ -1,8 +1,9 @@
import uuid
-
from pydantic import EmailStr
from sqlmodel import Field, Relationship, SQLModel
+from app.models.item import Item
+
# Shared properties
class UserBase(SQLModel):
@@ -43,7 +44,7 @@ class UpdatePassword(SQLModel):
class User(UserBase, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
hashed_password: str
- items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True)
+ items: list[Item] = Relationship(back_populates="owner", cascade_delete=True)
# Properties to return via API, id is always required
@@ -53,61 +54,4 @@ class UserPublic(UserBase):
class UsersPublic(SQLModel):
data: list[UserPublic]
- count: int
-
-
-# Shared properties
-class ItemBase(SQLModel):
- title: str = Field(min_length=1, max_length=255)
- description: str | None = Field(default=None, max_length=255)
-
-
-# Properties to receive on item creation
-class ItemCreate(ItemBase):
- pass
-
-
-# Properties to receive on item update
-class ItemUpdate(ItemBase):
- title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
-
-
-# Database model, database table inferred from class name
-class Item(ItemBase, table=True):
- id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
- owner_id: uuid.UUID = Field(
- foreign_key="user.id", nullable=False, ondelete="CASCADE"
- )
- owner: User | None = Relationship(back_populates="items")
-
-
-# Properties to return via API, id is always required
-class ItemPublic(ItemBase):
- id: uuid.UUID
- owner_id: uuid.UUID
-
-
-class ItemsPublic(SQLModel):
- data: list[ItemPublic]
- count: int
-
-
-# Generic message
-class Message(SQLModel):
- message: str
-
-
-# JSON payload containing access token
-class Token(SQLModel):
- access_token: str
- token_type: str = "bearer"
-
-
-# Contents of JWT token
-class TokenPayload(SQLModel):
- sub: str | None = None
-
-
-class NewPassword(SQLModel):
- token: str
- new_password: str = Field(min_length=8, max_length=40)
+ count: int
\ No newline at end of file
diff --git a/backend/app/repositories/item.py b/backend/app/repositories/item.py
new file mode 100644
index 0000000..a28e359
--- /dev/null
+++ b/backend/app/repositories/item.py
@@ -0,0 +1,12 @@
+import uuid
+from sqlmodel import Session
+
+from app.models import Item, ItemCreate
+
+
+def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
+ db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
+ session.add(db_item)
+ session.commit()
+ session.refresh(db_item)
+ return db_item
\ No newline at end of file
diff --git a/backend/app/crud.py b/backend/app/repositories/user.py
similarity index 58%
rename from backend/app/crud.py
rename to backend/app/repositories/user.py
index 905bf48..ad9cd52 100644
--- a/backend/app/crud.py
+++ b/backend/app/repositories/user.py
@@ -1,10 +1,9 @@
-import uuid
from typing import Any
from sqlmodel import Session, select
-from app.core.security import get_password_hash, verify_password
-from app.models import Item, ItemCreate, User, UserCreate, UserUpdate
+from app.core.security import get_password_hash
+from app.models import User, UserCreate, UserUpdate
def create_user(*, session: Session, user_create: UserCreate) -> User:
@@ -34,21 +33,4 @@ def update_user(*, session: Session, db_user: User, user_in: UserUpdate) -> Any:
def get_user_by_email(*, session: Session, email: str) -> User | None:
statement = select(User).where(User.email == email)
session_user = session.exec(statement).first()
- return session_user
-
-
-def authenticate(*, session: Session, email: str, password: str) -> User | None:
- db_user = get_user_by_email(session=session, email=email)
- if not db_user:
- return None
- if not verify_password(password, db_user.hashed_password):
- return None
- return db_user
-
-
-def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
- db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
- session.add(db_item)
- session.commit()
- session.refresh(db_item)
- return db_item
+ return session_user
\ No newline at end of file
diff --git a/backend/app/schemas/todo.py b/backend/app/schemas/todo.py
deleted file mode 100644
index 2332745..0000000
--- a/backend/app/schemas/todo.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from pydantic import BaseModel
-from datetime import datetime
-from typing import Optional
-
-class TodoBase(BaseModel):
- title: str
- description: Optional[str] = None
- completed: bool = False
-
-class TodoCreate(TodoBase):
- pass
-
-class TodoUpdate(TodoBase):
- pass
-
-class Todo(TodoBase):
- id: int
- created_at: datetime
- updated_at: Optional[datetime] = None
-
- class Config:
- from_attributes = True
\ No newline at end of file
diff --git a/backend/app/services/auth.py b/backend/app/services/auth.py
new file mode 100644
index 0000000..5c3c52a
--- /dev/null
+++ b/backend/app/services/auth.py
@@ -0,0 +1,13 @@
+from sqlmodel import Session
+
+from app.core.security import verify_password
+from app.repositories.user import get_user_by_email
+
+
+def authenticate(*, session: Session, email: str, password: str) -> User | None:
+ db_user = get_user_by_email(session=session, email=email)
+ if not db_user:
+ return None
+ if not verify_password(password, db_user.hashed_password):
+ return None
+ return db_user
\ No newline at end of file
diff --git a/backend/app/tests/api/routes/test_login.py b/backend/app/tests/api/routes/test_login.py
index 80fa787..a814205 100644
--- a/backend/app/tests/api/routes/test_login.py
+++ b/backend/app/tests/api/routes/test_login.py
@@ -9,7 +9,7 @@ from app.crud import create_user
from app.models import UserCreate
from app.tests.utils.user import user_authentication_headers
from app.tests.utils.utils import random_email, random_lower_string
-from app.utils import generate_password_reset_token
+from app.utils.common import generate_password_reset_token
def test_get_access_token(client: TestClient) -> None:
diff --git a/backend/app/utils.py b/backend/app/utils/common.py
similarity index 100%
rename from backend/app/utils.py
rename to backend/app/utils/common.py
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000..7bae48a
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,6 @@
+pydantic>=2.8.2
+sqlmodel>=0.0.14
+python-jose[cryptography]>=3.3.0
+passlib[bcrypt]>=1.7.4
+python-multipart>=0.0.6
+email-validator>=2.1.0.post1
\ No newline at end of file
diff --git a/scripts/generate-client.sh b/scripts/generate-client.sh
index 1e76864..f688c18 100644
--- a/scripts/generate-client.sh
+++ b/scripts/generate-client.sh
@@ -9,4 +9,4 @@ cd ..
mv openapi.json frontend/
cd frontend
npm run generate-client
-npx biome format --write ./src/client
+npx biome format --write ./src/client
\ No newline at end of file