Chore: Pushing changes to migrate from windows/wsl to fedora

This commit is contained in:
2025-07-25 21:27:16 +02:00
parent e60489715a
commit b0f9685a0a
16 changed files with 442 additions and 19 deletions
+3
View File
@@ -6,4 +6,7 @@
cd backend
alembic revision --autogenerate -m "Header message"
alembic upgrade head
# Making alembic DB is up-to-date without actually running the migration
alembic stamp head
```
@@ -0,0 +1,32 @@
"""Date_modified columns - add default=None
Revision ID: 174e0494276d
Revises: d89dba0432de
Create Date: 2025-06-15 20:42:30.850962
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '174e0494276d'
down_revision: Union[str, None] = 'd89dba0432de'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
@@ -0,0 +1,38 @@
"""Product id made nullable
Revision ID: 8aefa882e096
Revises: e8c4300db3cb
Create Date: 2025-06-15 19:33:39.299803
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision: str = '8aefa882e096'
down_revision: Union[str, None] = 'e8c4300db3cb'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('product', 'id',
existing_type=mysql.INTEGER(),
nullable=True,
autoincrement=True)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('product', 'id',
existing_type=mysql.INTEGER(),
nullable=False,
autoincrement=True)
# ### end Alembic commands ###
@@ -0,0 +1,32 @@
"""Product id made nullable using 'default=None'
Revision ID: b5ff3e70bd95
Revises: 8aefa882e096
Create Date: 2025-06-15 19:38:20.874456
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'b5ff3e70bd95'
down_revision: Union[str, None] = '8aefa882e096'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
@@ -0,0 +1,32 @@
"""Making Product.date_modified default=None
Revision ID: d89dba0432de
Revises: b5ff3e70bd95
Create Date: 2025-06-15 20:06:11.734486
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'd89dba0432de'
down_revision: Union[str, None] = 'b5ff3e70bd95'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
+63 -7
View File
@@ -1,18 +1,19 @@
"""
The Client table
The Clients endpoint
"""
from fastapi import APIRouter, HTTPException
from sqlmodel import Session, select
from sqlmodel import select
from app.api.deps import SessionDep, exists
from app.schemas.models import Client
from app.schemas.schemas import ClientCreate
from typing import Sequence
from app.schemas.schemas import ClientCreate, ClientUpdate
from pydantic import ValidationError
from typing import Sequence, List, Optional
router = APIRouter(prefix="/clients", tags=["clients"])
@router.get("/", response_model=Client)
def fetch_clients(client: Client, session: SessionDep) -> Sequence[Client]:
@router.get("/", response_model=List[Client])
def fetch_clients(session: SessionDep) -> Sequence[Client]:
"""Fetch client list
"""
clients = session.exec(select(Client)).all()
@@ -21,11 +22,66 @@ def fetch_clients(client: Client, session: SessionDep) -> Sequence[Client]:
@router.post("/", response_model=ClientCreate)
def create_client(client_data: ClientCreate, session: SessionDep) -> Client:
"""Create a client
"""
existing = exists(session, Client, tin_number=client_data.tin_number)
if existing:
raise HTTPException(status_code=400, detail="Client with this tin number already exists")
client = Client.model_validate(client_data)
try:
client = Client.model_validate(client_data)
except ValidationError as e:
raise HTTPException(status_code=400, detail=e.errors())
session.add(client)
session.commit()
session.refresh(client)
return client
@router.get("/{client_id}", response_model=Client)
def get_client(client_id: int, session: SessionDep) -> Optional[Client]:
"""Returns a client by its ID
"""
stmt = select(Client).where(Client.id == client_id)
result: Optional[Client] = session.exec(stmt).first()
if not result:
raise HTTPException(status_code=404, detail="Client not found")
return result
@router.patch("/{client_id}", response_model=ClientCreate)
def update_client(
client_id: int,
client_data: ClientUpdate,
session: SessionDep) -> Optional[Client]:
"""Updates a client using its ID
"""
client = session.get(Client, client_id)
if not client:
raise HTTPException(status_code=404, detail="Client not found")
update_fields = client_data.model_dump(exclude_unset=True)
for key, value in update_fields.items():
setattr(client, key, value)
session.add(client)
session.commit()
session.refresh(client)
return client
@router.delete("/{client_id}", status_code=204)
def delete_client(client_id: int, session: SessionDep):
"""Deletes a client
"""
client = session.get(Client, client_id)
if not client:
raise HTTPException(status_code=404, detail="Client not found")
session.delete(client)
session.commit()
-11
View File
@@ -1,11 +0,0 @@
"""
API Home
"""
from fastapi import APIRouter
from app.core.config import settings
api_router = APIRouter()
#if settings.environment == "local":
# api_router.include_router()
+99
View File
@@ -0,0 +1,99 @@
"""
The products endpoint
"""
from fastapi import APIRouter, HTTPException
from sqlmodel import select
from app.api.deps import SessionDep, exists
from app.schemas.models import Product
from app.schemas.schemas import ProductCreate, ProductUpdate
from pydantic import ValidationError
from typing import Sequence, List, Optional
router = APIRouter(prefix="/products", tags=["products"])
@router.get("/", response_model=List[Product])
def fetch_products(session: SessionDep) -> Sequence[Product]:
"""Fetch product list
"""
products = session.exec(select(Product)).all()
return products
@router.post("/", response_model=ProductCreate)
def create_product(product_data: ProductCreate, session: SessionDep) -> Product:
"""Create a product
"""
name_exists = exists(session, Product, product_name=product_data.product_name)
if name_exists:
raise HTTPException(
status_code=400,
detail="Product with this product_name exists"
)
existing = exists(session, Product, product_code=product_data.product_code)
if existing:
raise HTTPException(
status_code=400,
detail="Product with this product_code exists"
)
try:
product = Product.model_validate(product_data)
except ValidationError as e:
raise HTTPException(status_code=400, detail=e.errors())
session.add(product)
session.commit()
session.refresh(product)
return product
@router.get("/{product_id}", response_model=Product)
def get_product(product_id: int, session: SessionDep) -> Optional[Product]:
"""Returns a product by its id
"""
stmt = select(Product).where(Product.id == product_id)
result: Optional[Product] = session.exec(stmt).first()
if not result:
raise HTTPException(status_code=404, detail="Product not found")
return result
@router.patch("/{product_id}",response_model=ProductUpdate)
def update_product(
product_id: int,
product_data: ProductUpdate,
session: SessionDep
) -> Optional[Product]:
"""Updates a product
"""
product = session.get(Product, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
updated_fields = product_data.model_dump(exclude_unset=True)
for key, value in updated_fields.items():
setattr(product, key, value)
session.add(product)
session.commit()
session.refresh(product)
return product
@router.delete("/{product_id}", status_code=204)
def delete_product(product_id: int, session: SessionDep):
"""Deletes a product
"""
product = session.get(Product, product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
session.delete(product)
session.commit()
+88
View File
@@ -0,0 +1,88 @@
"""The suppliers endpoint
"""
from fastapi import APIRouter, HTTPException
from sqlmodel import select
from app.api.deps import SessionDep, exists
from app.schemas.models import Supplier
from app.schemas.schemas import SupplierCreate, SupplierUpdate
from pydantic import ValidationError
from typing import Sequence, List, Optional
router = APIRouter(prefix="/suppliers", tags=["suppliers"])
@router.get("/", response_model=List[Supplier])
def fetch_suppliers(session: SessionDep) -> Sequence[Supplier]:
"""Fetch supplier list
"""
suppliers = session.exec(select(Supplier)).all()
return suppliers
@router.post("/", response_model=SupplierCreate)
def create_supplier(supplier_data: SupplierCreate, session: SessionDep) -> Supplier:
"""Create a supplier
"""
existing = exists(session, Supplier, tin_number=supplier_data.tin_number)
if existing:
raise HTTPException(status_code=400, detail="Supplier with this tin_number already exists")
try:
supplier = Supplier.model_validate(supplier_data)
except ValidationError as e:
raise HTTPException(status_code=400, detail=e.errors())
session.add(supplier)
session.commit()
session.refresh(supplier)
return supplier
@router.get("/{supplier_id}", response_model=Supplier)
def get_supplier(supplier_id: int, session: SessionDep) -> Optional[Supplier]:
"""Returns a supplier by its ID
"""
stmt = select(Supplier).where(Supplier.id == supplier_id)
result: Optional[Supplier] = session.exec(stmt).first()
if not result:
raise HTTPException(status_code=404, detail="Supplier not found")
return result
@router.patch("/{supplier_id}", response_model=SupplierUpdate)
def update_supplier(
supplier_id: int,
supplier_data: SupplierUpdate,
session: SessionDep
) -> Optional[Supplier]:
"""Updates a supplier's details
"""
supplier = session.get(Supplier, supplier_id)
if not supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
updated_fields = supplier_data.model_dump(exclude_unset=True)
for key, value in updated_fields.items():
setattr(supplier, key, value)
session.add(supplier)
session.commit()
session.refresh(supplier)
return supplier
@router.delete("/{supplier_id}", status_code=204)
def delete_supplier(supplier_id: int, session: SessionDep):
"""Deletes a supplier
"""
supplier = session.get(Supplier, supplier_id)
if not supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
session.delete(supplier)
session.commit()
+4
View File
@@ -8,6 +8,8 @@ from app.core.config import settings
from typing import Union
from fastapi import FastAPI
from app.api.clients.endpoints import router as clients_router
from app.api.suppliers.endpoints import router as supplier_router
from app.api.products.endpoints import router as product_router
app = FastAPI(
@@ -23,3 +25,5 @@ def read_root():
return {"Hello": "World"}
app.include_router(clients_router, tags=["clients"])
app.include_router(supplier_router, tags=["suppliers"])
app.include_router(product_router, tags=["products"])
+4 -1
View File
@@ -49,11 +49,12 @@ class Product(SQLModel, table=True):
"""
__table_args__ = (UniqueConstraint("product_code"),)
id: Optional[int] = Field(nullable=False, primary_key=True)
id: Optional[int] = Field(default=None, primary_key=True)
product_code: str = Field(max_length=10, nullable=False)
product_name: str = Field(max_length=20, nullable=False, unique=True)
purchase_price: int = Field(nullable=False)
date_modified: datetime = Field(
default=None,
sa_column=Column(DateTime,
server_default=func.now(),
server_onupdate=func.now())
@@ -77,6 +78,7 @@ class Payment(SQLModel, table=True):
amount: int = Field(nullable=False)
payment_method: str = Field(max_length=24, nullable=False)
date: datetime = Field(
default=None,
sa_column=Column(DateTime, server_default=func.now())
)
@@ -96,5 +98,6 @@ class Credit(SQLModel, table=True):
qty: int = Field(nullable=False)
amount: int = Field(nullable=False)
date: datetime = Field(
default=None,
sa_column=Column(DateTime, server_default=func.now())
)
+31
View File
@@ -1,7 +1,38 @@
from sqlmodel import SQLModel
from typing import Optional
class ClientCreate(SQLModel):
tin_number: int
names: str
phone_number: str
class ClientUpdate(SQLModel):
tin_number: Optional[int] = None
names: Optional[str] = None
phone_number: Optional[str] = None
class SupplierCreate(SQLModel):
tin_number: int
names: str
phone_number: str
class SupplierUpdate(ClientUpdate):
tin_number: Optional[int] = None
names: Optional[str] = None
phone_number: Optional[str] = None
class ProductCreate(SQLModel):
product_code: str
product_name: str
purchase_price: int
class ProductUpdate(SQLModel):
product_code: Optional[str] = None
product_name: Optional[str] = None
purchase_price: Optional[int] = None