chore: initialize project structure, Docker setup, docker-compose on port 52867

This commit is contained in:
Experiments DB Dev
2026-04-15 10:38:41 -04:00
commit 44dfe3fdc4
9 changed files with 306 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
POSTGRES_USER=expuser
POSTGRES_PASSWORD=exppassword
POSTGRES_DB=experiments_db
DATABASE_URL=postgresql://expuser:exppassword@postgres:5432/experiments_db
NODE_ENV=production
+31
View File
@@ -0,0 +1,31 @@
# Environment
.env
.env.local
.env.*.local
# Node
node_modules/
npm-debug.log*
# Build outputs
dist/
build/
.next/
# Prisma
backend/prisma/migrations/dev.db
# Test artifacts
coverage/
test-results/
playwright-report/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
+14
View File
@@ -0,0 +1,14 @@
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM base AS dev
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS prod
COPY . .
RUN npx prisma generate
CMD ["npm", "start"]
+31
View File
@@ -0,0 +1,31 @@
version: '3.9'
# Development override — hot reload for backend, Vite dev server for frontend
# Usage: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
services:
postgres:
ports:
- "5432:5432"
backend:
build:
target: dev
volumes:
- ./backend:/app
- /app/node_modules
environment:
NODE_ENV: development
command: npm run dev
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
ports:
- "52867:5173"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
VITE_API_URL: http://localhost:3001
+57
View File
@@ -0,0 +1,57 @@
version: '3.9'
services:
postgres:
image: postgres:15-alpine
container_name: experiments_postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-expuser}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-exppassword}
POSTGRES_DB: ${POSTGRES_DB:-experiments_db}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-expuser} -d ${POSTGRES_DB:-experiments_db}"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: ./backend
target: prod
container_name: experiments_backend
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-expuser}:${POSTGRES_PASSWORD:-exppassword}@postgres:5432/${POSTGRES_DB:-experiments_db}
NODE_ENV: production
PORT: 3001
ports:
- "3001:3001"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"]
interval: 15s
timeout: 5s
retries: 5
frontend:
build:
context: ./frontend
target: prod
container_name: experiments_frontend
restart: unless-stopped
depends_on:
backend:
condition: service_healthy
ports:
- "52867:80"
volumes:
postgres_data:
name: experiments_postgres_data
+12
View File
@@ -0,0 +1,12 @@
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine AS prod
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+7
View File
@@ -0,0 +1,7 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
+23
View File
@@ -0,0 +1,23 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Serve static files, fall back to index.html for SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API calls to backend
location /api/ {
proxy_pass http://backend:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}
+126
View File
@@ -0,0 +1,126 @@
# Animal Experiments Database — Architecture Memory
## Project Overview
Full-stack application for managing animal experimental data.
- **Backend**: Node.js + Express + Prisma ORM
- **Frontend**: React (Vite) + TailwindCSS
- **Database**: PostgreSQL 15
- **Containerization**: Docker + Docker Compose
- **Main port**: 52867 (frontend), 3001 (backend API)
## Directory Structure
```
experiments-database/
├── backend/ # Node.js + Express + Prisma
│ ├── src/
│ │ ├── routes/ # Express route handlers
│ │ ├── middleware/ # Audit logging, error handling
│ │ └── prisma/ # Schema & migrations
│ ├── tests/ # Jest unit + integration tests
│ └── Dockerfile
├── frontend/ # React + Vite + TailwindCSS
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ └── api/ # Axios API client
│ ├── tests/ # RTL unit tests
│ ├── e2e/ # Playwright E2E tests
│ └── Dockerfile
├── docker-compose.yml
└── memory.md
```
## Database Schema
### Experiments
| Column | Type | Notes |
|------------|-----------|-------------------|
| id | UUID | PK, auto-generated |
| title | TEXT | NOT NULL |
| created_at | TIMESTAMP | auto-set |
### Animals
| Column | Type | Notes |
|------------------|------|----------------------|
| id | UUID | PK, auto-generated |
| experiment_id | UUID | FK → Experiments.id |
| animal_id_string | TEXT | Researcher's own ID |
| animal_name | TEXT | |
### Daily_Statuses
| Column | Type | Notes |
|------------------------|-----------|---------------------|
| id | UUID | PK, auto-generated |
| animal_id | UUID | FK → Animals.id |
| date | DATE | NOT NULL |
| experiment_description | TEXT | |
| vitals | TEXT | |
| treatment | TEXT | |
| notes | TEXT | |
### Audit_Logs
| Column | Type | Notes |
|------------|-----------|----------------------------|
| id | UUID | PK, auto-generated |
| table_name | TEXT | Which table was affected |
| record_id | UUID | The affected record's id |
| action | TEXT | CREATE / UPDATE / DELETE |
| changes | JSONB | before/after snapshot |
| timestamp | TIMESTAMP | auto-set |
## API Routes
### Experiments
- `GET /api/experiments` — list all
- `POST /api/experiments` — create
- `GET /api/experiments/:id` — get one
- `PUT /api/experiments/:id` — update (audited)
- `DELETE /api/experiments/:id` — delete (audited)
### Animals
- `GET /api/animals?experimentId=` — list for experiment
- `POST /api/animals` — create
- `GET /api/animals/:id` — get one
- `PUT /api/animals/:id` — update (audited)
- `DELETE /api/animals/:id` — delete (audited)
### Daily Statuses
- `GET /api/daily-statuses?animalId=` — list for animal
- `POST /api/daily-statuses` — create
- `GET /api/daily-statuses/:id` — get one
- `PUT /api/daily-statuses/:id` — update (audited)
- `DELETE /api/daily-statuses/:id` — delete (audited)
### Audit Logs
- `GET /api/audit-logs?tableName=&recordId=` — query logs
## Audit Middleware
All PUT and DELETE routes pass through `auditMiddleware` which:
1. Fetches the current record (before state)
2. Executes the route handler
3. Writes an `Audit_Log` entry with table_name, record_id, action, and changes (JSONB diff)
## Port Mapping
| Service | Internal | External |
|-----------|----------|----------|
| Frontend | 80 | 52867 |
| Backend | 3001 | 3001 |
| PostgreSQL| 5432 | 5432 |
## Git Branch Strategy
- `main` — stable, tested code
- `feature/docker-init` — Docker & project scaffolding
- `feature/database-setup` — Prisma schema + migrations
- `feature/backend-api` — Express routes + audit middleware
- `feature/frontend` — React UI
- `feature/tests` — Jest + RTL + Playwright tests
- `feature/red-team-fixes` — Security & UX patches
## Testing Notes
- Backend tests use Jest + Supertest against a test PostgreSQL instance
- Frontend unit tests use React Testing Library
- E2E tests use Playwright against the full Docker stack
- All tests run inside Docker via `docker-compose.test.yml`
## Red Team Audit
_To be filled after Phase 3 testing is complete._