Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/rules/backend-api-routers.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async def analyze(

## Dependency Injection

- **Auth**: `Depends(get_current_user)` — supports both Bearer JWT and `X-API-Key` header.
- **Auth**: `Depends(get_current_user)` — Bearer JWT only.
- **Database**: `Depends(get_db_session)` — yields an `AsyncSession`.
- **Services**: Instantiated in the handler body, not injected:
```python
Expand Down
3 changes: 1 addition & 2 deletions .cursor/rules/backend-infrastructure.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ alwaysApply: false

- Constructor takes `AsyncSession` — instantiated inside services, not injected by FastAPI.
- All queries enforce **user-scoped access**: `WHERE user_id = :user_id` on every read/write.
- Pattern: `UserRepository`, `JobRepository`, `ApiKeyRepository` in `db/repositories.py`.
- Pattern: `UserRepository`, `JobRepository` in `db/repositories.py`.

## Database Access

Expand All @@ -19,7 +19,6 @@ alwaysApply: false
- Session: `async_sessionmaker` with `expire_on_commit=False`.
- `get_db_session()` is an async generator yielding from `SessionLocal()` — used as `Depends()`.
- UUID primary keys, JSONB columns for flexible data (`input_metadata`, `result`).
- Soft delete for API keys via `revoked_at` timestamp.

## Docker Gateway (`docker/client.py`)

Expand Down
4 changes: 2 additions & 2 deletions README-compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Lifecycle scripts (mode-aware) are available:
- `./scripts/start.sh --dev`
- `./scripts/start.sh --prod`
- `./scripts/status.sh --dev|--prod`
- `./scripts/stop.sh --dev|--prod [--wipe]`
- `./scripts/stop.sh --dev|--prod [--wipe]` (`--wipe` in **dev** also clears `backend/storage/uploads` on disk)
- `.\scripts\start.ps1 --dev`
- `.\scripts\start.ps1 --prod`
- `.\scripts\status.ps1 --dev|--prod`
- `.\scripts\stop.ps1 --dev|--prod [--wipe]`
- `.\scripts\stop.ps1 --dev|--prod [--wipe]` (`--wipe` in **dev** also clears `backend/storage/uploads` on disk)

## 1) Prepare environment and secrets

Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ A multi-tenant platform for analyzing, scoring, and deploying Docker artifacts.
- **Project Archives**: Upload full `.zip` projects. The platform automatically detects Dockerfiles and Compose stacks, analyzes them, and prepares them for deployment.
- **Live Container Execution**: Deploy runnable Compose stacks directly from the UI to the underlying Docker daemon.
- **Real-time Monitoring**: Stream live container logs and view real-time CPU/Memory usage charts via WebSockets.
- **API Keys**: Generate API keys for programmatic access and CI/CD integration.
- **Analysis History**: Keep track of all your past analyses and deployments.

## 🏗️ Tech Stack & Architecture

- **Backend**: FastAPI (`backend/`) using modular-monolith + hexagonal architecture.
- **Frontend**: React SPA (`frontend/`) with Tailwind CSS, Recharts for monitoring, and real-time WebSocket integration.
- **Workers**: Redis event bus + arq workers for asynchronous analysis and deployment pipelines.
- **Database**: PostgreSQL for persistence (users, jobs, projects, containers, images, API keys).
- **Database**: PostgreSQL for persistence (users, jobs, projects, containers, images).
- **Proxy**: Nginx for serving the frontend and proxying API/WebSocket traffic.
- **Docker-in-Docker (DinD)**: We utilize a `dind` service to securely isolate the building and execution of user-submitted Dockerfiles and Compose stacks from the host system. The worker service communicates with this isolated daemon via TCP.

Expand Down Expand Up @@ -93,14 +92,14 @@ JWT_SECRET_KEY=choose-a-long-random-secret
./scripts/status.sh # compose + health summary
docker compose logs -f frontend api worker # tail live logs
./scripts/stop.sh # stop stack
./scripts/stop.sh --wipe # stop + wipe volumes
./scripts/stop.sh --wipe # stop + wipe volumes (+ dev upload dir on disk)
```

```powershell
.\scripts\status.ps1 # compose + health summary
docker compose logs -f frontend api worker # tail live logs
.\scripts\stop.ps1 # stop stack
.\scripts\stop.ps1 --wipe # stop + wipe volumes
.\scripts\stop.ps1 --wipe # stop + wipe volumes (+ dev upload dir on disk)
```

### Troubleshooting
Expand Down
5 changes: 4 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_MINUTES=10080
UPLOAD_DIR=storage/uploads
MAX_UPLOAD_SIZE_MB=30
MAX_UPLOAD_SIZE_MB=200
PROJECT_JOB_TIMEOUT_SECONDS=7200
IMAGE_BUILD_TIMEOUT_SECONDS=3600
MAX_IMAGE_BUILDS=3
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Production-grade FastAPI backend using a modular-monolith + hexagonal structure:

## Features

- Multi-user auth (`/auth/register`, `/auth/login`) with JWT + API key support
- Multi-user auth (`/auth/register`, `/auth/login`) with JWT Bearer tokens
- User-scoped jobs/history for Dockerfile, Compose, and project archive workflows
- Redis event bus with job/container event streams
- Plugin-based analyzers: hadolint, compose validator, compose runnability, security scanner, resource estimation
Expand Down
16 changes: 0 additions & 16 deletions backend/alembic/versions/0001_initial_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,8 @@ def upgrade() -> None:
)
op.create_index("ix_docker_images_user_id", "docker_images", ["user_id"], unique=False)

op.create_table(
"api_keys",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, nullable=False),
sa.Column("user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
sa.Column("key_prefix", sa.String(length=32), nullable=False),
sa.Column("key_hash", sa.String(length=255), nullable=False),
sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
)
op.create_index("ix_api_keys_user_id", "api_keys", ["user_id"], unique=False)
op.create_index("ix_api_keys_key_prefix", "api_keys", ["key_prefix"], unique=False)


def downgrade() -> None:
op.drop_index("ix_api_keys_key_prefix", table_name="api_keys")
op.drop_index("ix_api_keys_user_id", table_name="api_keys")
op.drop_table("api_keys")
op.drop_index("ix_docker_images_user_id", table_name="docker_images")
op.drop_table("docker_images")
op.drop_index("ix_containers_docker_container_id", table_name="containers")
Expand Down
16 changes: 2 additions & 14 deletions backend/app/api/deps.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import uuid
from datetime import UTC, datetime

from fastapi import Depends, Header, HTTPException
from fastapi import Depends, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import decode_token
from app.infrastructure.db.models import UserModel
from app.infrastructure.db.repositories import ApiKeyRepository, UserRepository
from app.infrastructure.db.repositories import UserRepository
from app.infrastructure.db.session import get_db_session

bearer_scheme = HTTPBearer(auto_error=False)


async def get_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
x_api_key: str | None = Header(default=None),
session: AsyncSession = Depends(get_db_session),
) -> UserModel:
user_repo = UserRepository(session)
api_key_repo = ApiKeyRepository(session)

if credentials:
try:
Expand All @@ -33,13 +30,4 @@ async def get_current_user(
except Exception as exc:
raise HTTPException(status_code=401, detail="Invalid bearer token.") from exc

if x_api_key:
key_record = await api_key_repo.get_by_raw_key(x_api_key)
if key_record:
key_record.last_used_at = datetime.now(UTC)
user = await user_repo.get_by_id(key_record.user_id)
await session.commit()
if user:
return user

raise HTTPException(status_code=401, detail="Authentication required.")
4 changes: 2 additions & 2 deletions backend/app/api/router.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from fastapi import APIRouter

from app.api.routers import api_keys, auth, compose, dockerfile, history, project, research, ws
from app.api.routers import auth, compose, dockerfile, history, preview_proxy, project, research, ws

api_router = APIRouter()
api_router.include_router(auth.router)
api_router.include_router(api_keys.router)
api_router.include_router(dockerfile.router)
api_router.include_router(compose.router)
api_router.include_router(preview_proxy.router)
api_router.include_router(project.router)
api_router.include_router(history.router)
api_router.include_router(research.router)
Expand Down
46 changes: 0 additions & 46 deletions backend/app/api/routers/api_keys.py

This file was deleted.

Loading
Loading