commit 39f1470940e8720a5fba1f83f7c29f63552d3c15 Author: Felix Zett Date: Fri Aug 1 22:35:14 2025 +0200 initial diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..4abfe9e --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..efab532 --- /dev/null +++ b/backend/database.py @@ -0,0 +1,8 @@ +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker +import os + +DATABASE_URL = os.environ.get("DATABASE_URL") + +engine = create_async_engine(DATABASE_URL, echo=True) +SessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..db51244 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +from models import Base +from database import engine + +app = FastAPI() + + +@app.on_event("startup") +async def startup(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + +@app.get("/") +def read_root(): + return {"status": "running"} diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..8ef781a --- /dev/null +++ b/backend/models.py @@ -0,0 +1,51 @@ +from sqlalchemy import Column, String, Boolean, Date, ForeignKey, Table, Text +from sqlalchemy.dialects.postgresql import UUID, ARRAY +from sqlalchemy.orm import relationship, declarative_base +import uuid + +Base = declarative_base() + +item_tags = Table( + "item_tags", + Base.metadata, + Column("item_id", UUID(as_uuid=True), ForeignKey("items.id")), + Column("tag_id", UUID(as_uuid=True), ForeignKey("tags.id")), +) + + +class Item(Base): + __tablename__ = "items" + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + user_id = Column(UUID(as_uuid=True), nullable=False) + name = Column(Text, nullable=False) + tags = relationship("Tag", secondary=item_tags, back_populates="items") + + +class Tag(Base): + __tablename__ = "tags" + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + user_id = Column(UUID(as_uuid=True), nullable=False) + name = Column(String, nullable=False, unique=False) + items = relationship("Item", secondary=item_tags, back_populates="tags") + + +class Trip(Base): + __tablename__ = "trips" + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + user_id = Column(UUID(as_uuid=True), nullable=False) + name = Column(String) + start_date = Column(Date) + end_date = Column(Date) + selected_tags = Column(ARRAY(String)) + marked_tags = Column(ARRAY(String)) + + +class TripItem(Base): + __tablename__ = "trip_items" + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + trip_id = Column(UUID(as_uuid=True), ForeignKey("trips.id")) + item_id = Column(UUID(as_uuid=True), ForeignKey("items.id"), nullable=True) + tag = Column(String, nullable=True) # e.g. "#kristin" + name = Column(Text) + calculated_label = Column(Text) + checked = Column(Boolean, default=False) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..4aceb6c --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,7 @@ +fastapi +uvicorn[standard] +sqlalchemy +asyncpg +pydantic +python-dotenv + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6682e8e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" + +services: + backend: + build: ./backend + ports: + - "8000:8000" + volumes: + - ./backend:/app + environment: + - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/packlist + + db: + image: postgres:15 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: packlist + volumes: + - db_data:/var/lib/postgresql/data + +volumes: + db_data: