From 8e12cb9bb66adc7adaf6d276133d6f235d294d23 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 15 Jul 2026 20:24:31 +0200 Subject: init --- order-service/app/api/deps.py | 10 ++++++ order-service/app/api/v1/api.py | 5 +++ order-service/app/api/v1/endpoints/orders.py | 48 ++++++++++++++++++++++++++++ order-service/app/core/config.py | 14 ++++++++ order-service/app/core/database.py | 8 +++++ order-service/app/init_db.py | 13 ++++++++ order-service/app/main.py | 19 +++++++++++ order-service/app/models/order.py | 41 ++++++++++++++++++++++++ order-service/app/schemas/order.py | 36 +++++++++++++++++++++ 9 files changed, 194 insertions(+) create mode 100644 order-service/app/api/deps.py create mode 100644 order-service/app/api/v1/api.py create mode 100644 order-service/app/api/v1/endpoints/orders.py create mode 100644 order-service/app/core/config.py create mode 100644 order-service/app/core/database.py create mode 100644 order-service/app/init_db.py create mode 100644 order-service/app/main.py create mode 100644 order-service/app/models/order.py create mode 100644 order-service/app/schemas/order.py (limited to 'order-service/app') diff --git a/order-service/app/api/deps.py b/order-service/app/api/deps.py new file mode 100644 index 0000000..d8dd349 --- /dev/null +++ b/order-service/app/api/deps.py @@ -0,0 +1,10 @@ +from typing import Generator +from app.core.database import SessionLocal + + +def get_db() -> Generator: + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/order-service/app/api/v1/api.py b/order-service/app/api/v1/api.py new file mode 100644 index 0000000..a967cb4 --- /dev/null +++ b/order-service/app/api/v1/api.py @@ -0,0 +1,5 @@ +from fastapi import APIRouter +from app.api.v1.endpoints import orders + +api_router = APIRouter() +api_router.include_router(orders.router, prefix="/orders", tags=["orders"]) diff --git a/order-service/app/api/v1/endpoints/orders.py b/order-service/app/api/v1/endpoints/orders.py new file mode 100644 index 0000000..4530958 --- /dev/null +++ b/order-service/app/api/v1/endpoints/orders.py @@ -0,0 +1,48 @@ +from decimal import Decimal +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session + +from app.api import deps +from app.models.order import Order, OrderItem +from app.schemas.order import OrderCreate, OrderOut + +router = APIRouter() + + +@router.post("/", response_model=OrderOut, status_code=status.HTTP_202_ACCEPTED) +def create_order(order_in: OrderCreate, db: Session = Depends(deps.get_db)): + """ + Submit a new customer order. + Returns status code 202 (Accepted) as the order is pending verification. + """ + try: + total = Decimal("0.00") + for item in order_in.items: + total += item.price * item.quantity + + db_order = Order( + customer_id=order_in.customer_id, status="PENDING", total_price=total + ) + db.add(db_order) + db.flush() + + for item in order_in.items: + db_item = OrderItem( + order_id=db_order.id, + product_id=item.product_id, + quantity=item.quantity, + price=item.price, + ) + db.add(db_item) + + db.commit() + db.refresh(db_order) + + return db_order + + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Could not initialize checkout process.", + ) diff --git a/order-service/app/core/config.py b/order-service/app/core/config.py new file mode 100644 index 0000000..013b356 --- /dev/null +++ b/order-service/app/core/config.py @@ -0,0 +1,14 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + PROJECT_NAME: str = "Order Service API" + + DATABASE_URL: str + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + + +settings = Settings() diff --git a/order-service/app/core/database.py b/order-service/app/core/database.py new file mode 100644 index 0000000..55d3e82 --- /dev/null +++ b/order-service/app/core/database.py @@ -0,0 +1,8 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import declarative_base, sessionmaker +from app.core.config import settings + +engine = create_engine(settings.DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() \ No newline at end of file diff --git a/order-service/app/init_db.py b/order-service/app/init_db.py new file mode 100644 index 0000000..7ee1465 --- /dev/null +++ b/order-service/app/init_db.py @@ -0,0 +1,13 @@ +from app.core.database import engine, Base + +from app.models.order import Order, OrderItem + + +def init_database(): + print("Creating database tables...") + Base.metadata.create_all(bind=engine) + print("Database initialized successfully! 🎉") + + +if __name__ == "__main__": + init_database() diff --git a/order-service/app/main.py b/order-service/app/main.py new file mode 100644 index 0000000..7837f11 --- /dev/null +++ b/order-service/app/main.py @@ -0,0 +1,19 @@ +import uvicorn +from fastapi import FastAPI +from app.api.v1.api import api_router +from app.core.config import settings + +app = FastAPI( + title=settings.PROJECT_NAME, +) + +app.include_router(api_router, prefix="/api/v1") + + +@app.get("/health") +def health_check(): + return {"status": "working"} + + +if __name__ == "__main__": + uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=True) diff --git a/order-service/app/models/order.py b/order-service/app/models/order.py new file mode 100644 index 0000000..1111269 --- /dev/null +++ b/order-service/app/models/order.py @@ -0,0 +1,41 @@ +from sqlalchemy import Column, Integer, Numeric, String, DateTime, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func +from app.core.database import Base + + +class Order(Base): + __tablename__ = "orders" + + id = Column(Integer, primary_key=True, index=True) + customer_id = Column(Integer, nullable=False, index=True) + status = Column(String(20), default="PENDING", nullable=False) + total_price = Column(Numeric(10, 2), nullable=False) + + created_at = Column( + DateTime(timezone=True), server_default=func.now(), nullable=False + ) + updated_at = Column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + + items = relationship( + "OrderItem", back_populates="order", cascade="all, delete-orphan" + ) + + +class OrderItem(Base): + __tablename__ = "order_items" + + id = Column(Integer, primary_key=True, index=True) + order_id = Column( + Integer, ForeignKey("orders.id", ondelete="CASCADE"), nullable=False + ) + product_id = Column(Integer, nullable=False) + quantity = Column(Integer, nullable=False) + price = Column(Numeric(10, 2), nullable=False) + + order = relationship("Order", back_populates="items") diff --git a/order-service/app/schemas/order.py b/order-service/app/schemas/order.py new file mode 100644 index 0000000..47aff2c --- /dev/null +++ b/order-service/app/schemas/order.py @@ -0,0 +1,36 @@ +from datetime import datetime +from decimal import Decimal +from pydantic import BaseModel, Field, ConfigDict + + +class OrderItemCreate(BaseModel): + product_id: int = Field(..., gt=0, description="Database ID of the product") + quantity: int = Field(..., gt=0, le=100, description="Must buy at least 1, max 100") + price: Decimal = Field(..., gt=0.00, description="The price per unit") + + +class OrderItemOut(BaseModel): + id: int + product_id: int + quantity: int + price: Decimal + + model_config = ConfigDict(from_attributes=True) + + +class OrderCreate(BaseModel): + customer_id: int = Field(..., gt=0) + items: list[OrderItemCreate] = Field( + ..., min_length=1, description="Order must contain at least 1 item" + ) + + +class OrderOut(BaseModel): + id: int + customer_id: int + status: str + total_price: Decimal + created_at: datetime + items: list[OrderItemOut] + + model_config = ConfigDict(from_attributes=True) -- cgit v1.2.3