diff --git a/.env.example b/.env.example index a44a386..0c83744 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,9 @@ # and CI/CD documentation. Replace placeholder values in real environments. # Docker Compose host ports for local inspection. +# Valid deployment modes are local and production. Local publishes service ports +# directly. Production publishes only Caddy ports 80/443 and requires ./Caddyfile. +DEPLOY_ENV=local MARIADB_HOST_PORT=3306 REDIS_HOST_PORT=6379 diff --git a/.gitignore b/.gitignore index fa58472..7b2e6ee 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ htmlcov/ # Environment variables .env +# Production deployment config +Caddyfile + # Local runtime logs backend.log frontend.log diff --git a/Caddyfile.example b/Caddyfile.example new file mode 100644 index 0000000..7cd10bc --- /dev/null +++ b/Caddyfile.example @@ -0,0 +1,3 @@ +example.com { + reverse_proxy frontend:80 +} diff --git a/README.md b/README.md index 10db201..5bc9b0b 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ If Gmail SMTP is used, use an app password rather than an account password. Scop ## Documentation - [`frontend/README.md`](frontend/README.md): frontend local development, production build, Docker, nginx proxying, and environment notes. -- [`webapi/README.md`](webapi/README.md): backend deployment, MariaDB operations, API checks, configuration, and troubleshooting. +- [`webapi/README.md`](webapi/README.md): backend deployment, MariaDB operations, API checks, [`admin` and `god` user elevation](webapi/README.md#administrative-users-and-roles), configuration, and troubleshooting. - [`webapi/tests/README.md`](webapi/tests/README.md): backend test commands and CI/CD test mapping. - [`plans/dbdiagram.md`](plans/dbdiagram.md): database and ORM diagram details for dbdiagram.io. diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..361c0ad --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,16 @@ +services: + mariadb: + ports: + - "${MARIADB_HOST_PORT:-3306}:3306" + + redis: + ports: + - "${REDIS_HOST_PORT:-6379}:6379" + + backend: + ports: + - "8000:8000" + + frontend: + ports: + - "80:80" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..90ceacf --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,16 @@ +services: + caddy: + image: caddy:2 + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + depends_on: + - frontend + +volumes: + caddy_data: + caddy_config: diff --git a/docker-compose.yml b/docker-compose.yml index 37e6add..feae478 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,6 @@ services: MARIADB_DATABASE: ${MARIADB_DATABASE:-crud_data} MARIADB_USER: ${MARIADB_USER:-webapi_user} MARIADB_PASSWORD: ${MARIADB_PASSWORD:-replace_with_local_database_password} - ports: - - "${MARIADB_HOST_PORT:-3306}:3306" volumes: - webapi-mariadb-data:/var/lib/mysql healthcheck: @@ -19,8 +17,6 @@ services: redis: image: redis:7-alpine - ports: - - "${REDIS_HOST_PORT:-6379}:6379" healthcheck: test: [ "CMD", "redis-cli", "ping" ] interval: 10s @@ -40,8 +36,8 @@ services: ENV_MAIL_PASSWORD: ${ENV_MAIL_PASSWORD:-replace_with_local_mail_app_password} ENV_MAIL_FROM: ${ENV_MAIL_FROM:-test@example.com} ENV_SECRET_KEY: ${ENV_SECRET_KEY:-replace_with_local_jwt_secret} - ports: - - "8000:8000" + expose: + - "8000" depends_on: mariadb: condition: service_healthy @@ -57,8 +53,8 @@ services: frontend: build: context: ./frontend - ports: - - "80:80" + expose: + - "80" depends_on: backend: condition: service_healthy diff --git a/frontend/src/components/prompts/prompt-detail-dialog.tsx b/frontend/src/components/prompts/prompt-detail-dialog.tsx new file mode 100644 index 0000000..6c0dcab --- /dev/null +++ b/frontend/src/components/prompts/prompt-detail-dialog.tsx @@ -0,0 +1,67 @@ +import type { PromptRecord } from '../../features/prompts/prompts-types' +import { Button } from '../ui/button' + +type PromptDetailDialogProps = { + prompt: PromptRecord | null + onClose: () => void + onEdit: (prompt: PromptRecord) => void + onDelete: (prompt: PromptRecord) => void +} + +export function PromptDetailDialog({ + prompt, + onClose, + onEdit, + onDelete, +}: PromptDetailDialogProps) { + if (!prompt) return null + + const handleEdit = () => { + onClose() + onEdit(prompt) + } + + const handleDelete = () => { + onClose() + onDelete(prompt) + } + + return ( +
{prompt.model_name}
+{prompt.prompt_text}
+