feat: initial scaffold — MVP catalogue de données d'API

- Backend NestJS : CRUD api_entries + categories, upload YAML multi-fichiers,
  génération docs AsyncAPI (@asyncapi/cli@6.0.0) et OpenAPI (redocly)
- Fix: route wildcard GET /api/apis/:id/*path pour servir les assets statiques
  (CSS/JS) générés par AsyncAPI HTML template (contournement bug path-to-regexp v8)
- Frontend Angular 19 : pages catalog, browse, api-detail, doc-viewer, upload (DSFR)
- Seeder de données de démo (idempotent)
- Docker Compose dev + Dockerfiles + manifests K8s
- Documentation : README, architecture, référence API REST

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
z3n
2026-05-22 08:42:54 +00:00
commit 921a6e652b
87 changed files with 16719 additions and 0 deletions

24
infra/Dockerfile.back.dev Normal file
View File

@@ -0,0 +1,24 @@
FROM node:22-alpine
# Install git (required by asyncapi html-template installer)
RUN apk add --no-cache git
# Install pnpm
RUN npm install -g pnpm@10.33.0
# Install AsyncAPI CLI and Redocly CLI for docs generation
RUN npm install -g @asyncapi/cli@6.0.0 @redocly/cli
# Pre-cache the AsyncAPI html-template so generation works on first startup
RUN mkdir -p /tmp/asyncapi-warmup /tmp/asyncapi-warmup/out && \
printf 'asyncapi: "3.0.0"\ninfo:\n title: Warmup\n version: "1.0.0"\nchannels: {}\n' \
> /tmp/asyncapi-warmup/spec.yaml && \
asyncapi generate fromTemplate /tmp/asyncapi-warmup/spec.yaml @asyncapi/html-template \
--output /tmp/asyncapi-warmup/out --no-interactive --install --force-write 2>&1 || true && \
rm -rf /tmp/asyncapi-warmup
WORKDIR /workspace
EXPOSE 3000
CMD ["pnpm", "run", "start:dev"]

View File

@@ -0,0 +1,10 @@
FROM node:22-alpine
# Install pnpm
RUN npm install -g pnpm@10.33.0
WORKDIR /workspace
EXPOSE 4200
CMD ["pnpm", "run", "start", "--", "--host", "0.0.0.0", "--poll", "500"]

113
infra/Dockerfile.prod Normal file
View File

@@ -0,0 +1,113 @@
# =============================================================================
# Dockerfile Multi-Target pour datacat
# Usage:
# docker build --target back -t datacat/back:latest .
# docker build --target front -t datacat/front:latest .
# =============================================================================
# -----------------------------------------------------------------------------
# Stage: base-node
# -----------------------------------------------------------------------------
FROM node:22-alpine AS base-node
RUN npm install -g pnpm@10.33.0
WORKDIR /app
# -----------------------------------------------------------------------------
# Stage: deps-back
# -----------------------------------------------------------------------------
FROM base-node AS deps-back
COPY pnpm-workspace.yaml ./
COPY package.json ./
COPY shared/package.json ./shared/
COPY back/package.json ./back/
RUN pnpm install --frozen-lockfile
# -----------------------------------------------------------------------------
# Stage: build-back
# -----------------------------------------------------------------------------
FROM deps-back AS build-back
COPY shared ./shared
COPY back ./back
WORKDIR /app/back
RUN pnpm run build
# -----------------------------------------------------------------------------
# Stage: deps-front
# -----------------------------------------------------------------------------
FROM base-node AS deps-front
COPY pnpm-workspace.yaml ./
COPY package.json ./
COPY shared/package.json ./shared/
COPY front-public/package.json ./front-public/
RUN pnpm install --frozen-lockfile
# -----------------------------------------------------------------------------
# Stage: build-front
# -----------------------------------------------------------------------------
FROM deps-front AS build-front
COPY shared ./shared
COPY front-public ./front-public
WORKDIR /app/front-public
ARG ANGULAR_API_URL=https://api.datacat.dev.chmod777.dev
ENV ANGULAR_API_URL=$ANGULAR_API_URL
RUN pnpm run build:prod
# =============================================================================
# IMAGES FINALES
# =============================================================================
# -----------------------------------------------------------------------------
# Target: back
# -----------------------------------------------------------------------------
FROM node:22-alpine AS back
# Install git (required by asyncapi html-template installer) and tools
RUN apk add --no-cache git
# Install AsyncAPI CLI and Redocly CLI for docs generation
RUN npm install -g pnpm@10.33.0 @asyncapi/cli@6.0.0 @redocly/cli
# Pre-cache the AsyncAPI html-template so generation works on first startup
RUN mkdir -p /tmp/asyncapi-warmup /tmp/asyncapi-warmup/out && \
printf 'asyncapi: "3.0.0"\ninfo:\n title: Warmup\n version: "1.0.0"\nchannels: {}\n' \
> /tmp/asyncapi-warmup/spec.yaml && \
asyncapi generate fromTemplate /tmp/asyncapi-warmup/spec.yaml @asyncapi/html-template \
--output /tmp/asyncapi-warmup/out --no-interactive --install --force-write 2>&1 || true && \
rm -rf /tmp/asyncapi-warmup
WORKDIR /app
COPY --from=build-back /app/node_modules ./node_modules
COPY --from=build-back /app/shared ./shared
COPY --from=build-back /app/back/dist ./back/dist
COPY --from=build-back /app/back/package.json ./back/
# Volume mount point for generated docs
RUN mkdir -p /app/docs-output
ENV NODE_ENV=production
ENV PORT=3000
ENV DOCS_OUTPUT_DIR=/app/docs-output
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
WORKDIR /app/back
CMD ["node", "dist/main"]
# -----------------------------------------------------------------------------
# Target: front
# -----------------------------------------------------------------------------
FROM nginx:alpine AS front
COPY infra/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build-front /app/front-public/dist/front-public/browser /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]

72
infra/docker-compose.yml Normal file
View File

@@ -0,0 +1,72 @@
name: datacat
services:
backend:
build:
context: ..
dockerfile: infra/Dockerfile.back.dev
ports:
- "3010:3000"
volumes:
- ..:/workspace
- back_node_modules:/workspace/back/node_modules
- shared_node_modules:/workspace/shared/node_modules
- docs_output:/app/docs-output
environment:
DATABASE_HOST: db
DATABASE_PORT: 5432
DATABASE_USER: datacat
DATABASE_PASSWORD: datacat
DATABASE_NAME: datacat
NODE_ENV: development
DOCS_OUTPUT_DIR: /app/docs-output
working_dir: /workspace/back
command: sh -c "cd /workspace && pnpm install && cd /workspace/back && pnpm run start:dev"
networks:
- network
depends_on:
db:
condition: service_healthy
frontend:
build:
context: ..
dockerfile: infra/Dockerfile.front.dev
ports:
- "4200:4200"
volumes:
- ..:/workspace
- front_node_modules:/workspace/front-public/node_modules
working_dir: /workspace/front-public
command: sh -c "cd /workspace && pnpm install && cd /workspace/front-public && node_modules/.bin/ng serve --host 0.0.0.0 --proxy-config proxy.conf.docker.json"
networks:
- network
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: datacat
POSTGRES_PASSWORD: datacat
POSTGRES_DB: datacat
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U datacat"]
interval: 5s
timeout: 5s
retries: 5
networks:
network:
driver: bridge
volumes:
back_node_modules:
shared_node_modules:
front_node_modules:
postgres_data:
docs_output:

83
infra/k8s/backend.yaml Normal file
View File

@@ -0,0 +1,83 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: datacat
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: git.chmod777.dev/z3n/datacat/back:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: PORT
value: "3000"
- name: DATABASE_HOST
value: postgres
- name: DATABASE_PORT
value: "5432"
- name: DATABASE_NAME
value: datacat
- name: DATABASE_USER
valueFrom:
secretKeyRef:
name: datacat-secrets
key: db-user
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: datacat-secrets
key: db-password
- name: DOCS_OUTPUT_DIR
value: /app/docs-output
volumeMounts:
- name: docs-output
mountPath: /app/docs-output
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: docs-output
persistentVolumeClaim:
claimName: datacat-docs-pvc
imagePullSecrets:
- name: registry-secret
---
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: datacat
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 3000

47
infra/k8s/frontend.yaml Normal file
View File

@@ -0,0 +1,47 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: datacat
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: git.chmod777.dev/z3n/datacat/front:latest
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 20
imagePullSecrets:
- name: registry-secret
---
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: datacat
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80

View File

@@ -0,0 +1,79 @@
# IngressRoutes pour datacat dans le namespace dev
# Frontend: https://datacat.dev.chmod777.dev
# API: https://api.datacat.dev.chmod777.dev
# --- Frontend HTTP → HTTPS redirect ---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: frontend-http
namespace: datacat
spec:
entryPoints: [web]
routes:
- match: Host(`datacat.dev.chmod777.dev`)
kind: Rule
middlewares:
- name: redirect-https
namespace: dev
services:
- name: frontend
port: 80
---
# --- Frontend HTTPS avec Basic Auth ---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: frontend-https
namespace: datacat
spec:
entryPoints: [websecure]
routes:
- match: Host(`datacat.dev.chmod777.dev`)
kind: Rule
middlewares:
- name: dev-basic-auth
namespace: dev
services:
- name: frontend
port: 80
tls:
certResolver: letsencrypt
---
# --- API HTTP → HTTPS redirect ---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: backend-http
namespace: datacat
spec:
entryPoints: [web]
routes:
- match: Host(`api.datacat.dev.chmod777.dev`)
kind: Rule
middlewares:
- name: redirect-https
namespace: dev
services:
- name: backend
port: 80
---
# --- API HTTPS avec Basic Auth ---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: backend-https
namespace: datacat
spec:
entryPoints: [websecure]
routes:
- match: Host(`api.datacat.dev.chmod777.dev`)
kind: Rule
middlewares:
- name: dev-basic-auth
namespace: dev
services:
- name: backend
port: 80
tls:
certResolver: letsencrypt

View File

@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- pvc.yaml
- postgres.yaml
- backend.yaml
- frontend.yaml
- ingressroutes.yaml

4
infra/k8s/namespace.yaml Normal file
View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: datacat

59
infra/k8s/postgres.yaml Normal file
View File

@@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: datacat
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:17-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: datacat-secrets
key: db-user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: datacat-secrets
key: db-password
- name: POSTGRES_DB
value: datacat
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: datacat-postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: datacat
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432

23
infra/k8s/pvc.yaml Normal file
View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datacat-docs-pvc
namespace: datacat
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datacat-postgres-pvc
namespace: datacat
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

26
infra/nginx.conf Normal file
View File

@@ -0,0 +1,26 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
# Angular SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# No cache for index.html
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}