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
6 changes: 2 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/database?schema=public"

# NextAuth
NEXTAUTH_SECRET="change-me-in-production"
NEXTAUTH_URL="http://localhost:3000"
# JWT Authentication
JWT_SECRET="change-me-minimum-32-chars-for-production-security"

# Google Maps API
# Server-side only (used for geocoding in batch jobs and API routes)
Expand All @@ -21,7 +20,6 @@ AWS_SECRET_ACCESS_KEY=""

# AWS S3 for image storage
AWS_S3_BUCKET_NAME="your-bucket-name"
AWS_S3_BUCKET="your-bucket-name"

# CDN Configuration (optional - if not set, falls back to S3 direct URLs)
# CloudFront distribution for faster image delivery
Expand Down
188 changes: 188 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
name: CI

on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
migration-safety:
name: Migration Safety Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'

steps:
- name: Checkout PR branch
uses: actions/checkout@v4

- name: Checkout base branch for comparison
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
path: base

- name: Check for destructive migration operations
run: |
echo "πŸ” Scanning migrations for destructive operations..."
echo ""

# Find new or modified migration files compared to base
NEW_MIGRATIONS=""
MODIFIED_MIGRATIONS=""

for file in prisma/migrations/*/migration.sql; do
[ -f "$file" ] || continue
migration_name=$(dirname "$file" | xargs basename)

if [ ! -f "base/$file" ]; then
NEW_MIGRATIONS="$NEW_MIGRATIONS $migration_name"
elif ! diff -q "$file" "base/$file" > /dev/null 2>&1; then
MODIFIED_MIGRATIONS="$MODIFIED_MIGRATIONS $migration_name"
fi
done

# Flag modified existing migrations (checksum will break Prisma)
if [ -n "$MODIFIED_MIGRATIONS" ]; then
echo "::error::❌ EXISTING MIGRATIONS MODIFIED β€” this will break Prisma checksum verification in production!"
echo ""
for m in $MODIFIED_MIGRATIONS; do
echo " β›” $m"
done
echo ""
echo "Prisma verifies checksums of applied migrations. Modifying an already-applied migration"
echo "will cause 'prisma migrate deploy' to fail in any environment where it was previously applied."
echo ""
FAILED=true
fi

# Scan new migrations for destructive patterns
DESTRUCTIVE_FOUND=false
DESTRUCTIVE_PATTERNS="DROP TABLE|DROP COLUMN|DROP INDEX|DROP CONSTRAINT|DROP TYPE|DROP ENUM|TRUNCATE|DELETE FROM|ALTER COLUMN.*TYPE|RENAME TABLE|RENAME COLUMN"

if [ -n "$NEW_MIGRATIONS" ]; then
echo "πŸ“‹ New migrations in this PR:"
for m in $NEW_MIGRATIONS; do
echo " βœ… $m"
done
echo ""

for m in $NEW_MIGRATIONS; do
file="prisma/migrations/$m/migration.sql"
# Search for destructive patterns (case-insensitive, ignoring comments)
matches=$(grep -inE "$DESTRUCTIVE_PATTERNS" "$file" | grep -v '^\s*--' || true)

if [ -n "$matches" ]; then
DESTRUCTIVE_FOUND=true
echo "::warning::⚠️ DESTRUCTIVE OPERATIONS found in $m"
echo "$matches" | while IFS= read -r line; do
echo " β†’ $line"
done
echo ""
fi
done
fi

if [ -n "$MODIFIED_MIGRATIONS" ]; then
echo ""
exit 1
fi

if [ "$DESTRUCTIVE_FOUND" = true ]; then
echo ""
echo "⚠️ Destructive operations detected. This PR requires EXPLICIT approval from a reviewer"
echo " who has verified that these operations are safe for the production database."
echo ""
echo " Reviewers: check that no production data is lost and that rollback is possible."
echo ""
# Warning only β€” does not block merge, but makes it very visible
exit 0
fi

if [ -z "$NEW_MIGRATIONS" ] && [ -z "$MODIFIED_MIGRATIONS" ]; then
echo "βœ… No migration changes in this PR"
else
echo "βœ… All new migrations are non-destructive (safe for production)"
fi

quality:
name: Quality Checks
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Type check
run: npm run type-check

- name: Lint
run: npm run lint

- name: Format check
run: npm run format:check
continue-on-error: true

test:
name: Tests
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Run unit tests
run: npm run test:unit

- name: Run integration tests
run: npm run test:integration

build:
name: Build
runs-on: ubuntu-latest
needs: [quality, test, migration-safety]
if: always() && needs.quality.result == 'success' && needs.test.result == 'success' && (needs.migration-safety.result == 'success' || needs.migration-safety.result == 'skipped')

env:
DATABASE_URL: "postgresql://dummy:dummy@localhost:5432/dummy"
JWT_SECRET: "ci-build-only-secret-not-for-production-use-32chars"

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Build
run: npx next build
env:
SKIP_ENV_VALIDATION: "true"
82 changes: 82 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# CLAUDE.md β€” Project Rules for DeaMap

## Project Overview
DeaMap is an AED (Automated External Defibrillator) management platform built with Next.js 15, Prisma ORM, and PostgreSQL with PostGIS. It uses Domain-Driven Design (DDD) architecture for the import module.

## Mandatory Rules

### Database Migrations (CRITICAL)
- **NEVER** create migrations that drop tables, columns, indexes, or constraints without explicit approval
- **NEVER** modify an existing migration file β€” Prisma verifies checksums and this will break production deployments
- **NEVER** use `DELETE FROM`, `TRUNCATE`, or `ALTER COLUMN ... TYPE` in migrations without explicit approval
- All new migrations must use `IF NOT EXISTS` / `IF EXISTS` guards where possible
- New migrations adding columns must use sensible `DEFAULT` values to avoid breaking existing rows
- The CI pipeline (`migration-safety` job) automatically detects destructive operations and blocks/warns on PRs
- When creating migrations that depend on other tables, verify the execution order (Prisma runs migrations alphabetically by directory name)

### Testing
- All PRs must pass the test suite (unit + integration)
- Tests are run with Vitest (not Jest). E2E tests use Playwright and are separate
- When modifying domain entities or value objects, update corresponding tests in `tests/`

### Code Style
- TypeScript strict mode is enabled
- ESLint and Prettier are configured β€” run `npm run lint` and `npm run format:check`
- Pre-commit hooks run type-check, lint, and build automatically

## Architecture

### Tech Stack
- **Framework**: Next.js 15 (App Router)
- **ORM**: Prisma with PostgreSQL + PostGIS
- **Auth**: JWT tokens via `jose` library, bcrypt for passwords
- **Maps**: Leaflet with MarkerCluster
- **Testing**: Vitest (unit/integration), Playwright (e2e)

### Directory Structure
```
src/
β”œβ”€β”€ app/ # Next.js App Router (pages + API routes)
β”œβ”€β”€ components/ # React components
β”œβ”€β”€ lib/ # Shared utilities (db, jwt, auth, etc.)
β”œβ”€β”€ import/ # DDD module for CSV import
β”‚ β”œβ”€β”€ domain/ # Entities, Value Objects, Repository interfaces
β”‚ β”œβ”€β”€ application/ # Use Cases
β”‚ └── infrastructure/ # Prisma implementations
β”œβ”€β”€ batch/ # Batch processing system
└── types/ # TypeScript type definitions

prisma/
β”œβ”€β”€ schema.prisma # Database schema
β”œβ”€β”€ migrations/ # Prisma migrations (NEVER modify existing ones)
└── seed-dummy.ts # Dummy data seeder for branch databases

scripts/
β”œβ”€β”€ migrate.js # Migration runner with branch database support
└── branch-database.js # Branch-specific database management
```

### Key Domain Concepts (Import Module)
- **CsvPreview**: Value Object β€” `create(headers: string[], sampleRows: string[][], totalRows: number)`
- **ValidationResult**: Value Object β€” `create(errors, warnings, stats)`, `withIssues()`, `success()`, `empty()`
- **ValidationError**: Value Object β€” `create({row, field, value, errorType, message, severity})`
- **ImportSession**: Entity β€” State machine: PREVIEW β†’ MAPPING β†’ VALIDATING β†’ READY β†’ IMPORTING β†’ COMPLETED
- **ColumnMapping**: Value Object β€” Maps CSV columns to database fields

### Branch Database System
- Feature branches on Vercel get isolated PostgreSQL databases
- Managed by `scripts/branch-database.js` and `scripts/migrate.js`
- New branch databases are seeded with 500 dummy DEAs and test users
- Test credentials: `admin@deamap.es` / `123456`

## Commands
```bash
npm run dev # Start development server
npm run build # Production build
npm run type-check # TypeScript check
npm run lint # ESLint
npm run format:check # Prettier check
npm run test:unit # Unit tests (Vitest)
npm run test:integration # Integration tests (Vitest)
npm run test:e2e # E2E tests (Playwright)
```
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ services:
image: postgis/postgis:17-3.4
container_name: dea-postgres
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: toor
POSTGRES_DB: samur_dea
POSTGRES_USER: ${POSTGRES_USER:-root}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-toor}
POSTGRES_DB: ${POSTGRES_DB:-samur_dea}
ports:
- "5555:5432"
volumes:
Expand Down
5 changes: 4 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import nextPlugin from "@next/eslint-plugin-next";
import reactHooksPlugin from "eslint-plugin-react-hooks";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand All @@ -27,6 +28,7 @@ const eslintConfig = [
plugins: {
"@typescript-eslint": tsPlugin,
"@next/next": nextPlugin,
"react-hooks": reactHooksPlugin,
},
languageOptions: {
parser: tsParser,
Expand Down Expand Up @@ -106,7 +108,8 @@ const eslintConfig = [
'no-console': 'off',
"prefer-const": "error",
"no-var": "error",
"react-hooks/exhaustive-deps": "off", // Plugin no disponible
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
},
];
Expand Down
40 changes: 27 additions & 13 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ const nextConfig: NextConfig = {
},
},

// Image optimization settings
// Image optimization settings - restricted to known image sources
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
hostname: "*.s3.*.amazonaws.com",
},
{
protocol: "https",
hostname: "*.cloudfront.net",
},
{
protocol: "https",
hostname: "*.sharepoint.com",
},
],
},
Expand All @@ -26,17 +34,7 @@ const nextConfig: NextConfig = {

productionBrowserSourceMaps: process.env.VERCEL_ENV === "preview",

// Ensure proper handling of API routes
async rewrites() {
return [
{
source: "/api/:path*",
destination: "/api/:path*",
},
];
},

// Add proper headers for security
// Security headers
async headers() {
return [
{
Expand All @@ -50,6 +48,22 @@ const nextConfig: NextConfig = {
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=(self), interest-cohort=()",
},
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
],
},
];
Expand Down
Loading
Loading