Skip to content

Commit b98ab37

Browse files
authored
Merge pull request #2 from CVSz/codex/generate-full-stack-application-project
Add zacino full-stack project (FastAPI backend, React frontend, Docker, CI)
2 parents 4b98e77 + ed46260 commit b98ab37

45 files changed

Lines changed: 1515 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

zacino/.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
POSTGRES_DB=zacino
2+
POSTGRES_USER=zacino
3+
POSTGRES_PASSWORD=change-me
4+
DATABASE_URL=postgresql+asyncpg://zacino:change-me@db:5432/zacino
5+
SECRET_KEY=replace-with-strong-secret
6+
ACCESS_TOKEN_EXPIRE_MINUTES=60
7+
CORS_ORIGINS=http://localhost:5173,http://localhost:4173
8+
AUTO_CREATE_DB=true
9+
VITE_API_URL=http://localhost:8000
10+
BACKEND_PORT=8000
11+
FRONTEND_PORT=5173

zacino/.gitea/workflows/ci.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
8+
jobs:
9+
build-test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Install backend dependencies
21+
working-directory: zacino/backend
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install -r requirements.txt -r requirements-dev.txt
25+
26+
- name: Lint backend
27+
working-directory: zacino/backend
28+
run: ruff check app tests
29+
30+
- name: Test backend
31+
working-directory: zacino/backend
32+
env:
33+
SECRET_KEY: test-secret
34+
run: pytest
35+
36+
- name: Set up Node
37+
uses: actions/setup-node@v4
38+
with:
39+
node-version: "20"
40+
41+
- name: Install frontend dependencies
42+
working-directory: zacino/frontend
43+
run: npm ci
44+
45+
- name: Lint frontend
46+
working-directory: zacino/frontend
47+
run: npm run lint
48+
49+
- name: Test frontend
50+
working-directory: zacino/frontend
51+
run: npm test
52+
53+
- name: Build frontend
54+
working-directory: zacino/frontend
55+
run: npm run build
56+
57+
- name: Build Docker images
58+
working-directory: zacino
59+
run: docker compose build
60+
61+
- name: Deploy (compose up)
62+
working-directory: zacino
63+
env:
64+
SECRET_KEY: ${{ secrets.SECRET_KEY }}
65+
run: |
66+
if [ -n "$SECRET_KEY" ]; then
67+
cp .env.example .env
68+
sed -i "s|replace-with-strong-secret|$SECRET_KEY|" .env
69+
docker compose up -d
70+
else
71+
echo "Skipping deploy: SECRET_KEY not set"
72+
fi
73+
74+
- name: Notify
75+
if: always()
76+
env:
77+
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
78+
run: |
79+
if [ -n "$WEBHOOK_URL" ]; then
80+
curl -X POST -H 'Content-Type: application/json' \
81+
-d '{"status":"'"$GITHUB_JOB"'","result":"'"${{ job.status }}"'"}' \
82+
"$WEBHOOK_URL"
83+
fi

zacino/.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*.egg-info/
5+
.venv/
6+
7+
# Node
8+
node_modules/
9+
/dist/
10+
11+
# Env
12+
.env
13+
14+
# Logs
15+
*.log
16+
17+
# OS
18+
.DS_Store

zacino/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 zacino
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

zacino/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# zacino
2+
3+
**zacino** is a Meta full-stack, high-performance optimization command center for enterprise-grade teams. It provides the **Meta Core Feature**: a secure, JWT-based optimization job queue with real-time lifecycle controls and scoring.
4+
5+
## Tech stack
6+
7+
- **Backend:** FastAPI + SQLAlchemy (async) + Uvicorn
8+
- **Frontend:** React + Vite + TypeScript
9+
- **Database:** PostgreSQL (Docker) / SQLite (local fallback)
10+
- **Auth:** JWT-based
11+
- **Testing:** Pytest + Vitest
12+
- **CI/CD:** Gitea Actions
13+
- **Deploy:** Docker Compose with `.env` configuration
14+
15+
## Project structure
16+
17+
```
18+
backend/ FastAPI service
19+
frontend/ React UI
20+
```
21+
22+
## Local setup
23+
24+
1. Copy environment variables:
25+
26+
```bash
27+
cp .env.example .env
28+
```
29+
30+
2. Backend (Python 3.12):
31+
32+
```bash
33+
cd backend
34+
python -m venv .venv
35+
source .venv/bin/activate
36+
pip install -r requirements.txt -r requirements-dev.txt
37+
uvicorn app.main:app --reload
38+
```
39+
40+
3. Frontend (Node 20+):
41+
42+
```bash
43+
cd frontend
44+
npm install
45+
npm run dev
46+
```
47+
48+
The frontend runs on `http://localhost:5173` and connects to the API on `http://localhost:8000`.
49+
50+
## Docker setup
51+
52+
```bash
53+
cp .env.example .env
54+
55+
docker compose up --build
56+
```
57+
58+
- Frontend: `http://localhost:5173`
59+
- Backend: `http://localhost:8000`
60+
61+
## Tests
62+
63+
Backend:
64+
65+
```bash
66+
cd backend
67+
pytest
68+
```
69+
70+
Frontend:
71+
72+
```bash
73+
cd frontend
74+
npm test
75+
```
76+
77+
## API overview
78+
79+
- `POST /api/v1/auth/register` - register user
80+
- `POST /api/v1/auth/login` - login and receive JWT
81+
- `GET /api/v1/jobs` - list jobs
82+
- `POST /api/v1/jobs` - create job
83+
- `PATCH /api/v1/jobs/{id}` - update job
84+
- `DELETE /api/v1/jobs/{id}` - delete job
85+
- `GET /healthz` - liveness
86+
- `GET /readyz` - readiness (DB check)
87+
88+
## Security notes
89+
90+
- Set a strong `SECRET_KEY` in `.env`.
91+
- Configure `CORS_ORIGINS` with approved domains.
92+
93+
## License
94+
95+
MIT License. See [LICENSE](./LICENSE).

zacino/backend/Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
ENV PYTHONDONTWRITEBYTECODE=1 \
6+
PYTHONUNBUFFERED=1
7+
8+
COPY requirements.txt ./
9+
RUN pip install --no-cache-dir -r requirements.txt
10+
11+
COPY app ./app
12+
13+
EXPOSE 8000
14+
15+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

zacino/backend/app/__init__.py

Whitespace-only changes.

zacino/backend/app/api/__init__.py

Whitespace-only changes.

zacino/backend/app/api/deps.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from fastapi import Depends, HTTPException, status
2+
from fastapi.security import OAuth2PasswordBearer
3+
from jose import JWTError, jwt
4+
from sqlalchemy import select
5+
from sqlalchemy.ext.asyncio import AsyncSession
6+
7+
from app.core.config import get_settings
8+
from app.core.security import verify_password
9+
from app.db.models import User
10+
from app.db.session import get_session
11+
12+
settings = get_settings()
13+
14+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.api_v1_prefix}/auth/login")
15+
16+
17+
async def get_db() -> AsyncSession:
18+
async for session in get_session():
19+
yield session
20+
21+
22+
async def get_current_user(
23+
token: str = Depends(oauth2_scheme),
24+
session: AsyncSession = Depends(get_db),
25+
) -> User:
26+
credentials_exception = HTTPException(
27+
status_code=status.HTTP_401_UNAUTHORIZED,
28+
detail="Could not validate credentials",
29+
headers={"WWW-Authenticate": "Bearer"},
30+
)
31+
try:
32+
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
33+
subject: str | None = payload.get("sub")
34+
except JWTError as exc:
35+
raise credentials_exception from exc
36+
37+
if not subject:
38+
raise credentials_exception
39+
40+
result = await session.execute(select(User).where(User.email == subject))
41+
user = result.scalar_one_or_none()
42+
if not user:
43+
raise credentials_exception
44+
if not user.is_active:
45+
raise HTTPException(status_code=403, detail="Inactive user")
46+
return user
47+
48+
49+
async def authenticate_user(session: AsyncSession, email: str, password: str) -> User | None:
50+
result = await session.execute(select(User).where(User.email == email))
51+
user = result.scalar_one_or_none()
52+
if not user:
53+
return None
54+
if not verify_password(password, user.hashed_password):
55+
return None
56+
return user
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from datetime import timedelta
2+
3+
from fastapi import APIRouter, Depends, HTTPException, status
4+
from pydantic import BaseModel, EmailStr, Field
5+
from sqlalchemy import select
6+
from sqlalchemy.ext.asyncio import AsyncSession
7+
8+
from app.api.deps import authenticate_user, get_db
9+
from app.core.config import get_settings
10+
from app.core.security import create_access_token, get_password_hash
11+
from app.db.models import User
12+
13+
router = APIRouter(prefix="/auth", tags=["auth"])
14+
settings = get_settings()
15+
16+
17+
class RegisterRequest(BaseModel):
18+
email: EmailStr
19+
password: str = Field(min_length=8, max_length=128)
20+
21+
22+
class TokenResponse(BaseModel):
23+
access_token: str
24+
token_type: str = "bearer"
25+
26+
27+
class LoginRequest(BaseModel):
28+
email: EmailStr
29+
password: str = Field(min_length=8, max_length=128)
30+
31+
32+
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
33+
async def register(payload: RegisterRequest, session: AsyncSession = Depends(get_db)) -> TokenResponse:
34+
result = await session.execute(select(User).where(User.email == payload.email))
35+
if result.scalar_one_or_none():
36+
raise HTTPException(status_code=400, detail="Email already registered")
37+
38+
user = User(email=payload.email, hashed_password=get_password_hash(payload.password))
39+
session.add(user)
40+
await session.commit()
41+
42+
access_token = create_access_token(subject=user.email)
43+
return TokenResponse(access_token=access_token)
44+
45+
46+
@router.post("/login", response_model=TokenResponse)
47+
async def login(payload: LoginRequest, session: AsyncSession = Depends(get_db)) -> TokenResponse:
48+
user = await authenticate_user(session, payload.email, payload.password)
49+
if not user:
50+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
51+
52+
access_token = create_access_token(
53+
subject=user.email, expires_delta=timedelta(minutes=settings.access_token_expire_minutes)
54+
)
55+
return TokenResponse(access_token=access_token)

0 commit comments

Comments
 (0)