diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf9a613 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/backups diff --git a/backend/Dockerfile b/backend/Dockerfile index ec0ee93..2370126 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,3 +1,4 @@ + FROM ghcr.io/astral-sh/uv:python3.13-alpine WORKDIR /app @@ -6,6 +7,9 @@ COPY pyproject.toml /app/ RUN uv sync -COPY ./ /app +COPY . /app + +# Run the FastAPI app CMD ["uv","run","uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/backend/app/__pycache__/crud.cpython-313.pyc b/backend/app/__pycache__/crud.cpython-313.pyc new file mode 100644 index 0000000..ab6117a Binary files /dev/null and b/backend/app/__pycache__/crud.cpython-313.pyc differ diff --git a/backend/app/__pycache__/database.cpython-313.pyc b/backend/app/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000..6f29c09 Binary files /dev/null and b/backend/app/__pycache__/database.cpython-313.pyc differ diff --git a/backend/app/__pycache__/main.cpython-313.pyc b/backend/app/__pycache__/main.cpython-313.pyc index 0bd08c0..05b2ec2 100644 Binary files a/backend/app/__pycache__/main.cpython-313.pyc and b/backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/backend/app/__pycache__/models.cpython-313.pyc b/backend/app/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..bb40868 Binary files /dev/null and b/backend/app/__pycache__/models.cpython-313.pyc differ diff --git a/backend/app/__pycache__/schemas.cpython-313.pyc b/backend/app/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000..e0ca0df Binary files /dev/null and b/backend/app/__pycache__/schemas.cpython-313.pyc differ diff --git a/backend/app/crud.py b/backend/app/crud.py new file mode 100644 index 0000000..c2fb5dc --- /dev/null +++ b/backend/app/crud.py @@ -0,0 +1,26 @@ +from sqlalchemy.orm import Session +from app import models, schemas + + +def get_item(db: Session, item_id: int): + return db.query(models.Item).filter(models.Item.id == item_id).first() + + +def get_items(db: Session, skip: int = 0, limit: int = 10): + return db.query(models.Item).offset(skip).limit(limit).all() + + +def create_item(db: Session, item: schemas.ItemCreate): + db_item = models.Item(**item.model_dump()) + db.add(db_item) + db.commit() + db.refresh(db_item) + return db_item + + +def delete_item(db: Session, item_id: int): + item = db.query(models.Item).filter(models.Item.id == item_id).first() + if item: + db.delete(item) + db.commit() + return item diff --git a/backend/app/database.py b/backend/app/database.py new file mode 100644 index 0000000..22c96b1 --- /dev/null +++ b/backend/app/database.py @@ -0,0 +1,11 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base +import os + +DATABASE_URL = os.getenv( + "DATABASE_URL", "postgresql://user:password@db:5432/mydatabase" +) + +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() diff --git a/backend/app/main.py b/backend/app/main.py index 29b6f66..cbdb685 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,8 +1,48 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Depends, HTTPException +from sqlalchemy.orm import Session +from . import schemas, crud +from .database import SessionLocal, engine, Base + +Base.metadata.create_all(bind=engine) app = FastAPI() -@app.get("/") -async def read_root(): - return {"hello": "world"} +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@app.get("/check-health") +def health_check(): + return {"Health": "Super Healthy!"} + + +@app.post("/items/", response_model=schemas.Item) +def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)): + return crud.create_item(db, item) + + +@app.get("/items/", response_model=list[schemas.Item]) +def read_items(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): + return crud.get_items(db, skip, limit) + + +@app.get("/items/{item_id}", response_model=schemas.Item) +def read_item(item_id: int, db: Session = Depends(get_db)): + db_item = crud.get_item(db, item_id) + if db_item is None: + raise HTTPException(status_code=404, detail="Item not found") + return db_item + + +@app.delete("/items/{item_id}", response_model=schemas.Item) +def delete_item(item_id: int, db: Session = Depends(get_db)): + item = crud.delete_item(db, item_id) + if item is None: + raise HTTPException(status_code=404, detail="Item not found") + return item diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..355eca9 --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,11 @@ +from sqlalchemy import JSON, Column, Integer, String +from .database import Base + + +class Item(Base): + __tablename__ = "items" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, index=True) + description = Column(String, nullable=True) + body = Column(JSON, nullable=False) diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..c900f57 --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel + + +class ItemBase(BaseModel): + name: str + description: str | None = None + + +class ItemCreate(ItemBase): + pass + + +class Item(ItemBase): + id: int + + class Config: + from_attributes = True diff --git a/backup/Dockerfile b/backup/Dockerfile new file mode 100644 index 0000000..522fd59 --- /dev/null +++ b/backup/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine:latest + +RUN apk add --no-cache docker-cli bash findutils postgresql-client + +COPY backup.sh /backup.sh +RUN chmod +x /backup.sh + +CMD ["/backup.sh"] + diff --git a/backup/backup.sh b/backup/backup.sh new file mode 100644 index 0000000..f018d88 --- /dev/null +++ b/backup/backup.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +echo "[INFO] Backup entry success" + +# === Configuration === +PG_CONTAINER_NAME=$(docker ps --filter "name=_db" --format "{{.Names}}" | head -n 1) +PG_USER="user" +PG_PASSWORD="password" +PG_DB="mydatabase" +BACKUP_DIR="/backups" +LOG_DIR="/backups/logs" +RETENTION_DAYS=7 +TIMESTAMP=$(date +"%Y%m%d-%H%M%S") +FILENAME="$BACKUP_DIR/backup-$TIMESTAMP.sql" +LOGFILE="$LOG_DIR/backup-$TIMESTAMP.log" + +# === Setup Directories === +mkdir -p "$BACKUP_DIR" +mkdir -p "$LOG_DIR" + +# === Begin Logging === +exec > "$LOGFILE" 2>&1 + +echo "[INFO] Backup script started at $TIMESTAMP" +echo "[INFO] Backing up database '$PG_DB' from container '$PG_CONTAINER_NAME'..." + +# === Perform Backup === +PGPASSWORD="$PG_PASSWORD" pg_dump -h db -U "$PG_USER" -d "$PG_DB" > "$FILENAME" + +if [ $? -eq 0 ]; then + echo "[INFO] Backup successful: $FILENAME" +else + echo "[ERROR] Backup failed!" >&2 + exit 1 +fi + +# === Rotate Old Backups === +echo "[INFO] Removing backups older than $RETENTION_DAYS days..." +find "$BACKUP_DIR" -type f -name "backup-*.sql" -mtime +$RETENTION_DAYS -exec rm -f {} \; + +# === Rotate Old Logs === +echo "[INFO] Removing logs older than $RETENTION_DAYS days..." +find "$LOG_DIR" -type f -name "backup-*.log" -mtime +$RETENTION_DAYS -exec rm -f {} \; + +echo "[INFO] Backup and cleanup completed at $(date +"%Y%m%d-%H%M%S")" + diff --git a/blogs-app/.vite/deps/_metadata.json b/blogs-app/.vite/deps/_metadata.json deleted file mode 100644 index 9222614..0000000 --- a/blogs-app/.vite/deps/_metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "hash": "d81b31a4", - "configHash": "5834786f", - "lockfileHash": "e3b0c442", - "browserHash": "a25f5c91", - "optimized": {}, - "chunks": {} -} \ No newline at end of file diff --git a/blogs-app/.vite/deps/package.json b/blogs-app/.vite/deps/package.json deleted file mode 100644 index 3dbc1ca..0000000 --- a/blogs-app/.vite/deps/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/docker-compose.yaml b/docker-compose.yaml index 63bebdc..6c93478 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,13 +11,22 @@ services: - postgres_data:/var/lib/postgresql/data networks: - app-network + backup: + build: ./backup + volumes: + - ./backups:/backups + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - db + networks: + - app-network backend: build: ./backend volumes: - ./backend/app:/app/app environment: - DATABASE_URL: postgres://user:password@db:5432/mydatabase + DATABASE_URL: postgresql://user:password@db:5432/mydatabase depends_on: - db ports: diff --git a/frontend/src/utils/BlogList.tsx b/frontend/src/utils/BlogList.tsx index cf084e7..5e441b7 100644 --- a/frontend/src/utils/BlogList.tsx +++ b/frontend/src/utils/BlogList.tsx @@ -1,5 +1,12 @@ -export function BlogList() { - fetch("public/blogs/*.md").then((ret) => console.log(ret.text())); +import { useState, useEffect } from "react"; - return
; +export function BlogList() { + const [content, setContent] = useState(""); + useEffect(() => { + fetch(`localhost:8000/get-blogs`) + .then((res) => res.text()) + .then(setContent); + }, []); + + return
{content}
; } diff --git a/frontend/src/utils/BlogViewer.tsx b/frontend/src/utils/BlogViewer.tsx index 60bb15d..e0e6802 100644 --- a/frontend/src/utils/BlogViewer.tsx +++ b/frontend/src/utils/BlogViewer.tsx @@ -7,7 +7,7 @@ export function BlogViewer() { const { slug } = useParams(); useEffect(() => { - fetch(`/blogs/${slug ?? "home"}.md`) + fetch(`localhost:8000/get-blogs/${slug}`) .then((res) => res.text()) .then(setContent); }, []);