diff --git a/.scripts/docker-build-and-push.sh b/.scripts/docker-build-and-push.sh index 1cf702a9..e836e6e9 100755 --- a/.scripts/docker-build-and-push.sh +++ b/.scripts/docker-build-and-push.sh @@ -8,10 +8,24 @@ version="${1:?version argument is required}" export COMPTASSE_VERSION="$version" -docker compose -f .workflows/build/compose.yml build comptasse +docker compose -f .workflows/build/compose.yml build api dashboard website -# All-in-one image (dashboard + API + CLI), used by https://comptasse.com/install.sh -docker tag "comptasse/comptasse:${version}" "${IMAGE_PREFIX}/comptasse:${version}" -docker tag "${IMAGE_PREFIX}/comptasse:${version}" "${IMAGE_PREFIX}/comptasse:latest" -docker push "${IMAGE_PREFIX}/comptasse:${version}" -docker push "${IMAGE_PREFIX}/comptasse:latest" \ No newline at end of file +# API image +docker tag "comptasse-api:${version}" "${IMAGE_PREFIX}/api:${version}" +docker tag "${IMAGE_PREFIX}/api:${version}" "${IMAGE_PREFIX}/api:latest" +docker push "${IMAGE_PREFIX}/api:${version}" +docker push "${IMAGE_PREFIX}/api:latest" + +# Dashboard image +docker tag "comptasse-dashboard:${version}" "${IMAGE_PREFIX}/dashboard:${version}" +docker tag "${IMAGE_PREFIX}/dashboard:${version}" "${IMAGE_PREFIX}/dashboard:latest" +docker push "${IMAGE_PREFIX}/dashboard:${version}" +docker push "${IMAGE_PREFIX}/dashboard:latest" + +# Website image +docker tag "comptasse-website:${version}" "${IMAGE_PREFIX}/website:${version}" +docker tag "${IMAGE_PREFIX}/website:${version}" "${IMAGE_PREFIX}/website:latest" +docker push "${IMAGE_PREFIX}/website:${version}" +docker push "${IMAGE_PREFIX}/website:latest" + +echo "Published ${IMAGE_PREFIX}/{api,dashboard,website}:${version} (+ latest)" \ No newline at end of file diff --git a/.workflows/build/Dockerfile b/.workflows/build/Dockerfile deleted file mode 100644 index 04abbcfc..00000000 --- a/.workflows/build/Dockerfile +++ /dev/null @@ -1,103 +0,0 @@ -# ============================================================================== -# Comptasse All-in-One Docker Image -# ============================================================================== -# Multi-stage build that packages API + Dashboard + CLI into a single image. -# PostgreSQL and S3-compatible storage must be provided by the user. -# -# Usage: -# docker build -f .workflows/build/Dockerfile -t comptasse/comptasse . -# docker run -d -p 3000:3000 -p 5173:5173 -v comptasse-data:/data \ -# -e SQL_DATABASE_URL=postgres://... \ -# -e STORAGE_ENDPOINT=https://... \ -# -e STORAGE_BUCKET_NAME=... \ -# -e STORAGE_ACCESS_KEY=... \ -# -e STORAGE_SECRET_KEY=... \ -# comptasse/comptasse -# ============================================================================== - -# --------------------------------------------------------------------------- -# Stage 1: Build all packages -# --------------------------------------------------------------------------- -FROM node:25.2.1-alpine AS base -ENV NODE_OPTIONS="--max-old-space-size=4096" -RUN npm install -g pnpm@10.26.1 - -FROM base AS build -WORKDIR /root -COPY . . -RUN pnpm install - -# Build API and its workspace dependencies -RUN pnpm --filter @comptasse/application-api... run build - -# Build Dashboard and its workspace dependencies -# Dashboard needs VITE_API_BASE_URL for the SPA build. -# We use /api which nginx will proxy to the Node.js API process. -ARG VITE_API_BASE_URL=/api -ARG VITE_WEBSITE_BASE_URL="" -RUN printf "VITE_API_BASE_URL=%s\nVITE_WEBSITE_BASE_URL=%s\n" \ - "$VITE_API_BASE_URL" "$VITE_WEBSITE_BASE_URL" \ - > packages/dashboard/.env -RUN pnpm --filter @comptasse/dashboard... run build - -# Stamp CLI version from VERSION file -RUN VER=$(cat VERSION | tr -d 'v[:space:]') && \ - sed -i "s/^VERSION=\".*\"/VERSION=\"$VER\"/" packages/cli/comptasse.sh && \ - printf '%s\n' "$VER" > packages/cli/version - -# --------------------------------------------------------------------------- -# Stage 2: Runtime image with all services -# --------------------------------------------------------------------------- -FROM node:25.2.1-alpine AS deploy -ENV NODE_ENV=production - -# Install runtime dependencies: -# - nginx: serve dashboard static files + proxy API -# - supervisor: manage multiple processes in one container -# - postgresql16-client: psql for schema management on startup -# - curl: health checks and CLI install script -# - bash: entrypoint script -RUN apk add --no-cache nginx supervisor postgresql16-client curl bash - -# Install pnpm (needed for running tools scripts) -RUN npm install -g pnpm@10.26.1 - -WORKDIR /app - -# Copy entire workspace root so pnpm symlinks in node_modules and -# per-package node_modules/@comptasse/* resolve correctly -COPY --from=build /root/ . - -# Remove non-essential files to reduce image size; keep src for packages -# whose scripts run via tsx in the entrypoint (tools, metadata) -RUN rm -rf packages/*/test packages/*/*.test.* packages/*/*.spec.* \ - packages/*/lib.test.ts packages/ui/node_modules && \ - # Create convenience symlinks for /app/ paths used by configs - ln -s packages/api /app/api && \ - ln -s packages/tools /app/tools && \ - ln -s packages/dashboard /app/dashboard && \ - ln -s packages/cli /app/cli && \ - ln -s packages/metadata /app/metadata - -# Copy runtime configs -COPY .workflows/build/supervisord.conf /etc/supervisord.conf -COPY .workflows/build/nginx.conf /etc/nginx/nginx.conf -COPY .workflows/build/entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -# Build-time smoke tests: catch packaging issues before the image is finalized -RUN cd /app/tools && pnpm exec tsx --version && \ - test -f /app/api/build/server.js && \ - test -f /app/dashboard/build/index.html && \ - test -x /app/cli/comptasse.sh && \ - nginx -t -c /etc/nginx/nginx.conf && \ - bash -n /entrypoint.sh && \ - echo "All smoke tests passed" - -# Create data directory for volumes -RUN mkdir -p /data - -# Expose API and Dashboard ports -EXPOSE 3000 5173 - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/.workflows/build/compose.example.yml b/.workflows/build/compose.example.yml index 3f3439b8..f6655a30 100644 --- a/.workflows/build/compose.example.yml +++ b/.workflows/build/compose.example.yml @@ -1,8 +1,9 @@ # ============================================================================== -# Comptasse Full Stack (Example) +# Comptasse Self-Hosted Stack (Example) # ============================================================================== -# Complete stack with bundled PostgreSQL and RustFS (S3-compatible storage). -# For users who want a ready-to-use setup without external infrastructure. +# API + Dashboard with bundled PostgreSQL and RustFS (S3-compatible storage). +# The website is hosted by the maintainers and is not part of self-hosted +# installs; the CLI is downloaded from GitHub Releases. # # Usage: # docker compose -f .workflows/build/compose.example.yml up -d @@ -12,25 +13,27 @@ # ============================================================================== services: - comptasse: - image: comptasse/comptasse:${COMPTASSE_VERSION:-dev} + api: + image: comptasse-api:${COMPTASSE_VERSION:-dev} + container_name: comptasse-api ports: - "${API_PORT:-3000}:3000" - - "${DASHBOARD_PORT:-5173}:5173" - volumes: - - comptasse-data:/data environment: + ENV: production + VERBOSE: "false" + PORT: "3000" + CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:5173} + COOKIES_DOMAIN: localhost + COOKIES_KEY: ${COOKIES_KEY:?COOKIES_KEY is required} + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + WEBSITE_BASE_URL: ${WEBSITE_BASE_URL:-https://comptasse.com} + DASHBOARD_BASE_URL: ${DASHBOARD_BASE_URL:-http://localhost:5173} SQL_DATABASE_URL: postgres://postgres:password@postgres:5432/comptasse STORAGE_ENDPOINT: http://rustfs:9000 STORAGE_BUCKET_NAME: comptasse-files STORAGE_ACCESS_KEY: admin STORAGE_SECRET_KEY: admin STORAGE_REGION: fr-par - COOKIES_DOMAIN: localhost - CORS_ORIGIN: "*" - API_BASE_URL: http://localhost:3000 - WEBSITE_BASE_URL: http://localhost:5173 - DASHBOARD_BASE_URL: http://localhost:5173 depends_on: postgres: condition: service_healthy @@ -38,10 +41,20 @@ services: condition: service_started restart: unless-stopped + dashboard: + image: comptasse-dashboard:${COMPTASSE_VERSION:-dev} + container_name: comptasse-dashboard + ports: + - "${DASHBOARD_PORT:-5173}:80" + depends_on: + - api + restart: unless-stopped + postgres: image: postgres:18.1 + container_name: comptasse-postgres volumes: - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password @@ -55,6 +68,7 @@ services: rustfs: image: rustfs/rustfs:latest + container_name: comptasse-rustfs volumes: - rustfs-data:/data environment: @@ -65,6 +79,5 @@ services: restart: unless-stopped volumes: - comptasse-data: postgres-data: - rustfs-data: + rustfs-data: \ No newline at end of file diff --git a/.workflows/build/compose.start.yml b/.workflows/build/compose.start.yml index 36d5ec89..bd144c85 100644 --- a/.workflows/build/compose.start.yml +++ b/.workflows/build/compose.start.yml @@ -25,7 +25,7 @@ services: POSTGRES_PASSWORD: admin POSTGRES_DB: default ports: - - "5432:5432" + - "${PROD_POSTGRES_PORT:-5432}:5432" volumes: - postgres_data:/var/lib/postgresql healthcheck: @@ -44,8 +44,8 @@ services: RUSTFS_SECRET_KEY: rustfsadmin RUSTFS_VOLUMES: /data ports: - - "9000:9000" - - "9001:9001" + - "${PROD_STORAGE_PORT:-9000}:9000" + - "${PROD_STORAGE_UI_PORT:-9001}:9001" volumes: - rustfs_data:/data init: true @@ -65,19 +65,21 @@ services: ports: - "3000:3000" environment: - ENV: development + ENV: production + VERBOSE: "false" PORT: "3000" CORS_ORIGIN: http://localhost:5173 COOKIES_DOMAIN: localhost COOKIES_KEY: development-secret-key-change-in-production-min-32-chars API_BASE_URL: http://localhost:3000 WEBSITE_BASE_URL: http://localhost:5173 + DASHBOARD_BASE_URL: http://localhost:5174 SQL_DATABASE_URL: postgres://postgres:admin@postgres:5432/default STORAGE_ENDPOINT: http://rustfs:9000 - STORAGE_BUCKET_NAME: comptasse-files STORAGE_ACCESS_KEY: rustfsadmin STORAGE_SECRET_KEY: rustfsadmin + STORAGE_REGION: fr-par MOLLIE_API_KEY: test_z8gjjnezmRNx7dretKbEr5vcCfADh9 depends_on: postgres: @@ -85,6 +87,14 @@ services: rustfs: condition: service_healthy + dashboard: + container_name: comptasse-prod-dashboard + image: comptasse-dashboard:${COMPTASSE_VERSION:-dev} + ports: + - "5174:80" + depends_on: + - api + website: container_name: comptasse-prod-website image: comptasse-website:${COMPTASSE_VERSION:-dev} @@ -93,4 +103,4 @@ services: volumes: postgres_data: - rustfs_data: + rustfs_data: \ No newline at end of file diff --git a/.workflows/build/compose.yml b/.workflows/build/compose.yml index 8a437c54..e51fa2c1 100644 --- a/.workflows/build/compose.yml +++ b/.workflows/build/compose.yml @@ -1,44 +1,75 @@ # ============================================================================== # Comptasse Build Compose # ============================================================================== -# Builds the all-in-one Docker image containing API + Dashboard + CLI. +# Builds the three production Docker images: api, dashboard, website. # # Usage: # docker compose -f .workflows/build/compose.yml build # docker compose -f .workflows/build/compose.yml up -d # -# Required environment variables: +# Required environment variables (runtime): # SQL_DATABASE_URL - PostgreSQL connection string # STORAGE_ENDPOINT - S3-compatible storage endpoint # STORAGE_BUCKET_NAME - Storage bucket name # STORAGE_ACCESS_KEY - Storage access key # STORAGE_SECRET_KEY - Storage secret key +# +# Build arguments (dashboard/website): +# VITE_API_BASE_URL default /api +# VITE_WEBSITE_BASE_URL default empty +# VITE_DASHBOARD_BASE_URL default empty # ============================================================================== services: - comptasse: - image: comptasse/comptasse:${COMPTASSE_VERSION:-dev} + api: + image: comptasse-api:${COMPTASSE_VERSION:-dev} build: context: ../.. - dockerfile: .workflows/build/Dockerfile + dockerfile: .workflows/build/packages/api/Dockerfile ports: - "${API_PORT:-3000}:3000" - - "${DASHBOARD_PORT:-5173}:5173" - volumes: - - comptasse-data:/data environment: + ENV: ${ENV:-production} + VERBOSE: ${VERBOSE:-false} + PORT: "${API_PORT:-3000}" + CORS_ORIGIN: ${CORS_ORIGIN:-localhost} + COOKIES_DOMAIN: ${COOKIES_DOMAIN:-localhost} + COOKIES_KEY: ${COOKIES_KEY:-} + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + WEBSITE_BASE_URL: ${WEBSITE_BASE_URL:-http://localhost:5173} + DASHBOARD_BASE_URL: ${DASHBOARD_BASE_URL:-http://localhost:5173} SQL_DATABASE_URL: ${SQL_DATABASE_URL:-postgres://postgres:password@postgres:5432/comptasse} STORAGE_ENDPOINT: ${STORAGE_ENDPOINT:-http://rustfs:9000} STORAGE_BUCKET_NAME: ${STORAGE_BUCKET_NAME:-comptasse-files} STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY:-placeholder} STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY:-placeholder} STORAGE_REGION: ${STORAGE_REGION:-fr-par} - COOKIES_KEY: ${COOKIES_KEY:-} - COOKIES_DOMAIN: ${COOKIES_DOMAIN:-localhost} - CORS_ORIGIN: ${CORS_ORIGIN:-*} - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - WEBSITE_BASE_URL: ${WEBSITE_BASE_URL:-http://localhost:5173} - DASHBOARD_BASE_URL: ${DASHBOARD_BASE_URL:-http://localhost:5173} + restart: unless-stopped -volumes: - comptasse-data: + dashboard: + image: comptasse-dashboard:${COMPTASSE_VERSION:-dev} + build: + context: ../.. + dockerfile: .workflows/build/packages/dashboard/Dockerfile + args: + VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api} + VITE_WEBSITE_BASE_URL: ${VITE_WEBSITE_BASE_URL:-} + VITE_DASHBOARD_BASE_URL: ${VITE_DASHBOARD_BASE_URL:-} + ports: + - "${DASHBOARD_PORT:-5173}:80" + depends_on: + - api + restart: unless-stopped + + website: + image: comptasse-website:${COMPTASSE_VERSION:-dev} + build: + context: ../.. + dockerfile: .workflows/build/packages/website/Dockerfile + args: + VITE_WEBSITE_BASE_URL: ${VITE_WEBSITE_BASE_URL:-} + VITE_API_BASE_URL: ${VITE_API_BASE_URL:-} + VITE_DASHBOARD_BASE_URL: ${VITE_DASHBOARD_BASE_URL:-} + ports: + - "${WEBSITE_PORT:-8080}:80" + restart: unless-stopped \ No newline at end of file diff --git a/.workflows/build/entrypoint.sh b/.workflows/build/entrypoint.sh deleted file mode 100644 index f37a472c..00000000 --- a/.workflows/build/entrypoint.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -set -e - -# ============================================================================== -# Comptasse All-in-One Entrypoint -# ============================================================================== -# Handles: -# 1. Auto-generating COOKIES_KEY if not provided -# 2. Schema management (auto-push if no tables, check if tables exist) -# 3. Starting all services via supervisord -# ============================================================================== - -DATA_DIR="/data" -CONFIG_FILE="$DATA_DIR/config.json" - -# ------------------------------------------------------------------------------ -# 1. COOKIES_KEY management -# ------------------------------------------------------------------------------ -if [ -z "$COOKIES_KEY" ]; then - if [ -f "$CONFIG_FILE" ] && grep -q "COOKIES_KEY" "$CONFIG_FILE" 2>/dev/null; then - # Extract COOKIES_KEY from existing config - COOKIES_KEY=$(cat "$CONFIG_FILE" | grep COOKIES_KEY | sed 's/.*"COOKIES_KEY":\s*"\([^"]*\)".*/\1/') - echo "[entrypoint] Loaded COOKIES_KEY from $CONFIG_FILE" - else - # Generate a new random key - COOKIES_KEY=$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 32) - echo "[entrypoint] Generated new COOKIES_KEY" - - # Write config file - mkdir -p "$DATA_DIR" - echo "{\"COOKIES_KEY\":\"$COOKIES_KEY\"}" > "$CONFIG_FILE" - chmod 600 "$CONFIG_FILE" - fi - export COOKIES_KEY -else - echo "[entrypoint] Using provided COOKIES_KEY" - # Save provided key to config if not already there - if [ ! -f "$CONFIG_FILE" ] || ! grep -q "COOKIES_KEY" "$CONFIG_FILE" 2>/dev/null; then - mkdir -p "$DATA_DIR" - echo "{\"COOKIES_KEY\":\"$COOKIES_KEY\"}" > "$CONFIG_FILE" - chmod 600 "$CONFIG_FILE" - fi -fi - -# ------------------------------------------------------------------------------ -# 2. Schema management -# ------------------------------------------------------------------------------ -echo "[entrypoint] Checking database schema..." - -# Count tables in public schema -TABLE_COUNT=$(psql "$SQL_DATABASE_URL" -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public'" 2>/dev/null | tr -d '[:space:]') - -if [ "$TABLE_COUNT" = "0" ] || [ -z "$TABLE_COUNT" ]; then - echo "[entrypoint] No tables found. Pushing schema..." - cd /app/tools - pnpm run push - echo "[entrypoint] Schema pushed successfully." -else - echo "[entrypoint] Found $TABLE_COUNT tables. Verifying schema..." - cd /app/tools - ENV="production" VERBOSE="false" PORT="3000" SCHEMA_CHECK_ONLY=1 \ - node /app/api/build/server.js - CHECK_EXIT=$? - - if [ $CHECK_EXIT -ne 0 ]; then - echo "" - echo "[entrypoint] ERROR: Database schema is out of sync." - echo "" - echo "To fix this, you have several options:" - echo "" - echo " 1. Push schema (safe, no data loss):" - echo " docker exec comptasse pnpm --filter @comptasse/application-tools run push" - echo "" - echo " 2. Reset database (DELETES ALL DATA):" - echo " docker exec comptasse pnpm --filter @comptasse/application-tools run reset" - echo "" - echo " 3. Refer to migration documentation:" - echo " https://comptasse.com/documentation/guide/migrations" - echo "" - exit 1 - fi - echo "[entrypoint] Schema check passed." -fi - -# ------------------------------------------------------------------------------ -# 3. Install CLI in container -# ------------------------------------------------------------------------------ -if [ -f /app/cli/comptasse.sh ]; then - mkdir -p /usr/local/bin - cp /app/cli/comptasse.sh /usr/local/bin/comptasse - chmod +x /usr/local/bin/comptasse - echo "[entrypoint] CLI installed at /usr/local/bin/comptasse" -fi - -# ------------------------------------------------------------------------------ -# 4. Start services -# ------------------------------------------------------------------------------ -echo "[entrypoint] Starting services..." -exec supervisord -c /etc/supervisord.conf diff --git a/.workflows/build/nginx.conf b/.workflows/build/nginx.conf deleted file mode 100644 index af22276d..00000000 --- a/.workflows/build/nginx.conf +++ /dev/null @@ -1,51 +0,0 @@ -worker_processes auto; - -events { - worker_connections 1024; -} - -http { - include mime.types; - - # Dashboard SPA (port 5173) - server { - listen 5173; - - gzip on; - gzip_http_version 1.1; - gzip_disable "MSIE6"; - gzip_min_length 256; - gzip_vary on; - gzip_proxied any; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript font/woff2; - gzip_comp_level 9; - - root /app/dashboard/build; - index index.html; - - # Immutable hashed assets (JS, CSS, fonts, images) - cache for 1 year - location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|webmanifest|ico)$ { - add_header Cache-Control "public, max-age=31536000, immutable"; - try_files $uri =404; - } - - # SPA fallback - all routes served by index.html - location / { - add_header Cache-Control "no-cache"; - try_files $uri $uri/ /index.html =404; - } - - # Proxy API requests to Node.js backend - location /api/ { - proxy_pass http://127.0.0.1:3000/; - 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_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } - } -} diff --git a/.workflows/build/packages/api/Dockerfile b/.workflows/build/packages/api/Dockerfile index 202a747c..016e3892 100644 --- a/.workflows/build/packages/api/Dockerfile +++ b/.workflows/build/packages/api/Dockerfile @@ -1,20 +1,69 @@ -# Use an official Node.js image to build our image from +# ============================================================================== +# Comptasse API Docker Image +# ============================================================================== +# Multi-stage build that compiles the API + workspace dependencies, generates +# the SQL migrations (full schema setup + delta from the last release snapshot), +# and packages a self-contained production runtime with production deps only. +# +# Runtime environment (required by the API, see packages/api/src/utilities/getEnv.ts): +# ENV=production VERBOSE=false PORT=3000 +# CORS_ORIGIN COOKIES_DOMAIN COOKIES_KEY +# API_BASE_URL WEBSITE_BASE_URL DASHBOARD_BASE_URL +# SQL_DATABASE_URL +# STORAGE_ENDPOINT STORAGE_BUCKET_NAME STORAGE_ACCESS_KEY STORAGE_SECRET_KEY [STORAGE_REGION] +# [OCR_API_KEY OCR_ENDPOINT OCR_MODEL] +# +# On startup the entrypoint runs build/migrate.js (creates the schema on a fresh +# database, applies incremental deltas on upgrades) then starts the server. +# ============================================================================== + +# --------------------------------------------------------------------------- +# Stage 1: Build +# --------------------------------------------------------------------------- FROM node:25.2.1-alpine AS base ENV NODE_OPTIONS="--max-old-space-size=4096" RUN npm install -g pnpm@10.26.1 -# Build only what the API runtime needs FROM base AS build WORKDIR /root COPY . . RUN pnpm install + +# Build API and its workspace dependencies (metadata, ...) RUN pnpm --filter @comptasse/application-api... run build -# Create a self-contained deploy output with production dependencies only +# Generate SQL migrations: 0000_setup.sql (full current schema) + +# 0001_from_last_update.sql (delta vs the committed baseline snapshot; empty +# file if the schema did not change). +RUN cd packages/tools && MIGRATIONS_OUT=/tmp/migrations pnpm exec tsx ./src/generateMigrations.ts + +# Create a self-contained deploy output with production dependencies only. +# Workspace dependencies (e.g. @comptasse/application-metadata) are vendored +# inside the deploy output's node_modules/.pnpm store, so the runtime stage +# only needs this directory. RUN pnpm --filter @comptasse/application-api deploy --legacy --prod /deploy -# Start api with only Node.js runtime +# --------------------------------------------------------------------------- +# Stage 2: Runtime +# --------------------------------------------------------------------------- FROM node:25.2.1-alpine AS deploy -WORKDIR /root +ENV NODE_ENV=production +WORKDIR /app + COPY --from=build /deploy . -CMD ["node", "./build/server.js"] +COPY --from=build /tmp/migrations ./migrations +COPY .workflows/build/packages/api/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Build-time smoke tests: catch packaging issues before the image is finalized. +RUN test -f build/server.js && \ + test -f build/migrate.js && \ + node --check build/server.js && \ + node --check build/migrate.js && \ + test -f migrations/0000_setup.sql && \ + test -f migrations/0001_from_last_update.sql && \ + sh -n /entrypoint.sh && \ + echo "API smoke tests passed" + +EXPOSE 3000 +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/.workflows/build/packages/api/entrypoint.sh b/.workflows/build/packages/api/entrypoint.sh new file mode 100644 index 00000000..85e3d70a --- /dev/null +++ b/.workflows/build/packages/api/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e + +cd /app + +echo "[api] Running database migrations..." +node build/migrate.js + +echo "[api] Starting server on port ${PORT:-3000}..." +exec node build/server.js \ No newline at end of file diff --git a/.workflows/build/packages/dashboard/Dockerfile b/.workflows/build/packages/dashboard/Dockerfile index 96a5efbc..8e9a5b18 100644 --- a/.workflows/build/packages/dashboard/Dockerfile +++ b/.workflows/build/packages/dashboard/Dockerfile @@ -1,32 +1,52 @@ -# Use an official Node.js image to build our image from +# ============================================================================== +# Comptasse Dashboard Docker Image +# ============================================================================== +# Builds the dashboard SPA (Vite) and serves it with nginx. +# +# Build arguments (Vite environment variables baked into the SPA): +# VITE_API_BASE_URL default /api (proxied by nginx to the api service) +# VITE_WEBSITE_BASE_URL default empty +# VITE_DASHBOARD_BASE_URL default empty +# +# The nginx config also proxies /api/ to the `api` service on the compose +# network, which is how self-hosted installs reach the API from the browser. +# ============================================================================== + +# --------------------------------------------------------------------------- +# Stage 1: Build +# --------------------------------------------------------------------------- FROM node:25.2.1-alpine AS base RUN npm install -g pnpm@10.26.1 ENV NODE_OPTIONS="--max-old-space-size=4096" -# Build the repo FROM base AS build - -# Build arguments for Vite environment variables -ARG VITE_API_BASE_URL -ARG VITE_WEBSITE_BASE_URL +ARG VITE_API_BASE_URL=/api +ARG VITE_WEBSITE_BASE_URL="" +ARG VITE_DASHBOARD_BASE_URL="" WORKDIR /root COPY . . RUN pnpm install # Write VITE_* build args to .env so Vite can read them during build. -# Vite reads import.meta.env from .env files, not from process.env. -RUN printf "VITE_API_BASE_URL=%s\nVITE_WEBSITE_BASE_URL=%s\n" \ - "$VITE_API_BASE_URL" "$VITE_WEBSITE_BASE_URL" \ +RUN printf "VITE_API_BASE_URL=%s\nVITE_WEBSITE_BASE_URL=%s\nVITE_DASHBOARD_BASE_URL=%s\n" \ + "$VITE_API_BASE_URL" "$VITE_WEBSITE_BASE_URL" "$VITE_DASHBOARD_BASE_URL" \ > packages/dashboard/.env RUN pnpm --filter @comptasse/dashboard... run build -# Start application +# --------------------------------------------------------------------------- +# Stage 2: Runtime (nginx) +# --------------------------------------------------------------------------- FROM nginx:alpine AS deploy -WORKDIR / COPY .workflows/build/packages/dashboard/nginx/default.conf /etc/nginx/nginx.conf RUN rm -rf /usr/share/nginx/html/* COPY --from=build /root/packages/dashboard/build /usr/share/nginx/html + +# Build-time smoke tests +RUN nginx -t && \ + test -f /usr/share/nginx/html/index.html && \ + echo "Dashboard smoke tests passed" + EXPOSE 80 -CMD ["nginx"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/.workflows/build/packages/dashboard/nginx/default.conf b/.workflows/build/packages/dashboard/nginx/default.conf index aaf3a86b..5e385e38 100644 --- a/.workflows/build/packages/dashboard/nginx/default.conf +++ b/.workflows/build/packages/dashboard/nginx/default.conf @@ -1,15 +1,12 @@ -# default.conf worker_processes auto; -daemon off; - events { worker_connections 1024; } http { include mime.types; - + server { listen 80; @@ -25,8 +22,28 @@ http { root /usr/share/nginx/html; index index.html; + # Resolve the `api` service through Docker's embedded DNS (127.0.0.11). + # The resolver is only used when /api is requested. + resolver 127.0.0.11 valid=10s ipv6=off; + + # Proxy API requests to the api service on the compose network. + # Used when the SPA is built with VITE_API_BASE_URL=/api (self-hosted). + # When the SPA is built against a public API URL this location is unused. + location /api/ { + set $api_host api; + proxy_pass http://$api_host:3000/; + 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_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + # Immutable hashed assets (JS, CSS, fonts, images) - cache for 1 year - location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|webmanifest|ico)$ { + location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|webmanifest)$ { add_header Cache-Control "public, max-age=31536000, immutable"; try_files $uri =404; } @@ -37,4 +54,4 @@ http { try_files $uri $uri/ /index.html =404; } } -} +} \ No newline at end of file diff --git a/.workflows/build/packages/website/Dockerfile b/.workflows/build/packages/website/Dockerfile index bca139a7..faeb5d1f 100644 --- a/.workflows/build/packages/website/Dockerfile +++ b/.workflows/build/packages/website/Dockerfile @@ -1,30 +1,68 @@ -# Use an official Node.js image to build our image from +# ============================================================================== +# Comptasse Website Docker Image +# ============================================================================== +# Builds the marketing website (Vite) and serves it with nginx. +# Also serves the CLI install scripts and version file under /cli/ and the +# self-host installer at /install.sh (copied from the public dir by Vite). +# +# Build arguments (Vite environment variables baked into the SPA): +# VITE_WEBSITE_BASE_URL the public website URL +# VITE_API_BASE_URL the public API URL +# VITE_DASHBOARD_BASE_URL the public dashboard URL +# ============================================================================== + +# --------------------------------------------------------------------------- +# Stage 1: Build +# --------------------------------------------------------------------------- FROM node:25.2.1-alpine AS base RUN npm install -g pnpm@10.26.1 ENV NODE_OPTIONS="--max-old-space-size=4096" -# Build the repo FROM base AS build - -# Build argument for the website base URL (used for absolute links in generated .md files) ARG VITE_WEBSITE_BASE_URL +ARG VITE_API_BASE_URL +ARG VITE_DASHBOARD_BASE_URL WORKDIR /root COPY . . RUN pnpm install # Write VITE_* build args to .env so Vite can read them during build. -# Vite reads import.meta.env from .env files, not from process.env. -RUN printf "VITE_WEBSITE_BASE_URL=%s\n" "$VITE_WEBSITE_BASE_URL" \ +RUN printf "VITE_WEBSITE_BASE_URL=%s\nVITE_API_BASE_URL=%s\nVITE_DASHBOARD_BASE_URL=%s\n" \ + "$VITE_WEBSITE_BASE_URL" "$VITE_API_BASE_URL" "$VITE_DASHBOARD_BASE_URL" \ > packages/website/.env RUN pnpm --filter @comptasse/website... run build -# Serve +# Stamp the CLI version served at /cli/version from the VERSION file +RUN VER=$(cat VERSION | tr -d 'v[:space:]') && \ + printf '%s\n' "$VER" > packages/cli/version && \ + printf '%s\n' "$VER" > packages/website/build/cli/version + +# Generate the SQL migration files (0000_setup.sql + 0001_from_last_update.sql) +# served at /migrations/ so users can review the current release's schema SQL. +# Uses the same committed baseline snapshot (drizzle/meta/_snapshot.json) as the +# API image, so both images expose the exact same files. +RUN cd packages/tools && MIGRATIONS_OUT=/root/packages/website/build/migrations \ + pnpm exec tsx ./src/generateMigrations.ts + +# --------------------------------------------------------------------------- +# Stage 2: Runtime (nginx) +# --------------------------------------------------------------------------- FROM nginx:alpine AS deploy -WORKDIR / COPY .workflows/build/packages/website/nginx/default.conf /etc/nginx/nginx.conf RUN rm -rf /usr/share/nginx/html/* COPY --from=build /root/packages/website/build /usr/share/nginx/html + +# Build-time smoke tests +RUN nginx -t && \ + test -f /usr/share/nginx/html/index.html && \ + test -f /usr/share/nginx/html/install.sh && \ + test -f /usr/share/nginx/html/cli/install.sh && \ + test -f /usr/share/nginx/html/cli/version && \ + test -f /usr/share/nginx/html/migrations/0000_setup.sql && \ + test -f /usr/share/nginx/html/migrations/0001_from_last_update.sql && \ + echo "Website smoke tests passed" + EXPOSE 80 -CMD ["nginx"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/.workflows/build/packages/website/nginx/default.conf b/.workflows/build/packages/website/nginx/default.conf index 829166c5..8a1b81ff 100644 --- a/.workflows/build/packages/website/nginx/default.conf +++ b/.workflows/build/packages/website/nginx/default.conf @@ -1,8 +1,5 @@ -# default.conf worker_processes auto; -daemon off; - events { worker_connections 1024; } @@ -23,26 +20,39 @@ http { gzip_comp_level 9; root /usr/share/nginx/html; - index index.html; - # Raw markdown pages (.md) — served as text/markdown, no HTML, for LLM agents. - location ~* \.md$ { - types { text/markdown md; } - charset utf-8; + # HTML and other mutable files - always revalidate. + # Use a blank SPA shell (__app.html) as the fallback for routes that + # have no prerendered file (e.g. /dashboard). index.html contains + # the prerendered home page and must not be served as a catch-all. + location / { + index index.html; add_header Cache-Control "no-cache"; - try_files $uri =404; + try_files $uri $uri/ /__app.html =404; } + location = /favicon.ico { add_header Cache-Control "public, max-age=86400"; try_files $uri =404; } + location = /og.png { add_header Cache-Control "public, max-age=86400"; try_files $uri =404; } + location = /og.webp { add_header Cache-Control "public, max-age=86400"; try_files $uri =404; } - # Immutable hashed assets (JS, CSS, fonts, images) - cache for 1 year - location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|webmanifest|ico)$ { - add_header Cache-Control "public, max-age=31536000, immutable"; + # CLI install scripts and version file - serve as plain text, no caching + location ~* ^/cli/(.*\.sh|version)$ { + add_header Cache-Control "no-cache"; + add_header Content-Type "text/plain; charset=utf-8"; try_files $uri =404; } - # SPA fallback - all routes served by index.html - location / { + # SQL migration files for the current release - browsable folder tree, no caching + location /migrations/ { + alias /usr/share/nginx/html/migrations/; + autoindex on; + types { text/plain sql; } add_header Cache-Control "no-cache"; - try_files $uri $uri/ /index.html =404; + } + + # Immutable hashed assets (JS, CSS, fonts, images) - cache for 1 year + location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|webmanifest)$ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; } } -} +} \ No newline at end of file diff --git a/.workflows/build/supervisord.conf b/.workflows/build/supervisord.conf deleted file mode 100644 index 156a435f..00000000 --- a/.workflows/build/supervisord.conf +++ /dev/null @@ -1,42 +0,0 @@ -[supervisord] -nodaemon=true -user=root -logfile=/dev/null -logfile_maxbytes=0 -pidfile=/var/run/supervisord.pid - -[program:nginx] -command=nginx -g "daemon off;" -autostart=true -autorestart=true -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:api] -command=node ./api/build/server.js -directory=/app -autostart=true -autorestart=true -startsecs=5 -startretries=3 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -environment= - PORT="3000", - ENV="production", - VERBOSE="false", - COOKIES_DOMAIN="%(ENV_COOKIES_DOMAIN)s", - COOKIES_KEY="%(ENV_COOKIES_KEY)s", - API_BASE_URL="%(ENV_API_BASE_URL)s", - WEBSITE_BASE_URL="%(ENV_WEBSITE_BASE_URL)s", - DASHBOARD_BASE_URL="%(ENV_DASHBOARD_BASE_URL)s", - SQL_DATABASE_URL="%(ENV_SQL_DATABASE_URL)s", - STORAGE_ENDPOINT="%(ENV_STORAGE_ENDPOINT)s", - STORAGE_BUCKET_NAME="%(ENV_STORAGE_BUCKET_NAME)s", - STORAGE_ACCESS_KEY="%(ENV_STORAGE_ACCESS_KEY)s", - STORAGE_SECRET_KEY="%(ENV_STORAGE_SECRET_KEY)s", - STORAGE_REGION="%(ENV_STORAGE_REGION)s" diff --git a/justfile b/justfile index 49933c06..5491758d 100644 --- a/justfile +++ b/justfile @@ -78,16 +78,16 @@ build-cli: printf '%s\n' "$VER" > packages/cli/version && \ echo "CLI stamped: $VER" -# Build all-in-one Docker image (api + dashboard + cli) -build-all-in-one: +# Build all three production Docker images (api, dashboard, website) +build-images: @echo "==============================================" - @echo " Comptasse All-in-One Image Build" + @echo " Comptasse Image Build ($(cat VERSION))" @echo "==============================================" @echo "" - COMPTASSE_VERSION=$(cat VERSION) {{COMPOSE_BUILD}} build --no-cache comptasse + COMPTASSE_VERSION=$(cat VERSION) {{COMPOSE_BUILD}} build --no-cache api dashboard website @echo "" @echo "==============================================" - @echo " Image built: comptasse/comptasse ($(cat VERSION))" + @echo " Images built: comptasse-{api,dashboard,website} ($(cat VERSION))" @echo "==============================================" # Run CI gate: lint + typecheck + unit tests + build @@ -96,25 +96,12 @@ build-ci: @echo " Comptasse Build (lint + test + build)" @echo "==============================================" @echo "" - COMPTASSE_VERSION=$(cat VERSION) {{COMPOSE_BUILD}} build --no-cache comptasse + COMPTASSE_VERSION=$(cat VERSION) {{COMPOSE_BUILD}} build --no-cache api dashboard website @echo "" @echo "==============================================" @echo " Build succeeded" @echo "==============================================" -# Build production images - all-in-one only -# Runs the CI gate first, then builds the comptasse image tagged with VERSION -build-images: - @echo "==============================================" - @echo " Comptasse Image Build (ci + comptasse)" - @echo "==============================================" - @echo "" - COMPTASSE_VERSION=$(cat VERSION) {{COMPOSE_BUILD}} build --no-cache comptasse - @echo "" - @echo "==============================================" - @echo " Image built: comptasse/comptasse ($(cat VERSION))" - @echo "==============================================" - # Start production images against local infrastructure to check for startup errors # Requires images to be built first: just build images # Stops the dev environment first to free ports, then starts production images diff --git a/packages/api/src/migrate.ts b/packages/api/src/migrate.ts new file mode 100644 index 00000000..e3eb7bca --- /dev/null +++ b/packages/api/src/migrate.ts @@ -0,0 +1,73 @@ +import { readdirSync, readFileSync } from "node:fs" +import { join } from "node:path" +import postgres from "postgres" + +const databaseUrl = process.env.SQL_DATABASE_URL +if (!databaseUrl) { + console.error("[migrate] SQL_DATABASE_URL is required") + process.exit(1) +} + +const migrationsDir = process.env.MIGRATIONS_DIR || "/app/migrations" + +const sql = postgres(databaseUrl, { max: 1 }) + +async function main() { + await sql`create schema if not exists meta` + await sql`create table if not exists meta._migrations ( + name text primary key, + applied_at timestamptz not null default now() + )` + + const files = readdirSync(migrationsDir).filter((file) => file.endsWith(".sql")).sort() + if (files.length === 0) { + throw new Error(`No migration files found in ${migrationsDir}`) + } + + const appliedRows = await sql<{ name: string }[]>`select name from meta._migrations` + const applied = new Set(appliedRows.map((row) => row.name)) + + const [{ count }] = await sql<{ count: number }[]>`select count(*)::int as count from information_schema.tables where table_schema = 'public'` + const isFresh = count === 0 + + // 0000_setup.sql must be the first file (sorts before 0001_*) + const setupFile = files[0] + + for (const file of files) { + if (applied.has(file)) continue + + if (isFresh && file !== setupFile) { + // Fresh install: setup.sql contains the full current schema. + // Record the remaining files as applied without running them. + await sql`insert into meta._migrations (name) values (${file})` + continue + } + + if (!isFresh && file === setupFile) { + // Existing install: the database predates migrations, assume the + // schema is current and only run the incremental delta files. + await sql`insert into meta._migrations (name) values (${file})` + continue + } + + const body = readFileSync(join(migrationsDir, file), "utf8").trim() + if (body) { + await sql.unsafe(body) + } + await sql`insert into meta._migrations (name) values (${file})` + console.log(`[migrate] applied ${file}`) + } + + console.log(`[migrate] ${files.length} migration files, database schema is up to date.`) + await sql.end() +} + +main().catch(async (error) => { + console.error("[migrate] Migration failed:", error) + try { + await sql.end() + } catch { + // ignore + } + process.exit(1) +}) \ No newline at end of file diff --git a/packages/tools/drizzle/.gitignore b/packages/tools/drizzle/.gitignore new file mode 100644 index 00000000..281abc32 --- /dev/null +++ b/packages/tools/drizzle/.gitignore @@ -0,0 +1,3 @@ +* +!meta/ +!meta/_snapshot.json \ No newline at end of file diff --git a/packages/tools/drizzle/0001_add_token_tracking_and_subagents.sql b/packages/tools/drizzle/0001_add_token_tracking_and_subagents.sql deleted file mode 100644 index 0f830c2d..00000000 --- a/packages/tools/drizzle/0001_add_token_tracking_and_subagents.sql +++ /dev/null @@ -1,46 +0,0 @@ --- Add token tracking to organization, agent sessions, and agent messages --- Also add subagent support fields to agent messages - --- Organization: token-based billing -ALTER TABLE "table_organization" -ADD COLUMN "agent_tokens_current_month_usage" integer NOT NULL DEFAULT 0; - --- Agent session: running token aggregates -ALTER TABLE "table_agent_session" -ADD COLUMN "total_prompt_tokens" integer NOT NULL DEFAULT 0; - -ALTER TABLE "table_agent_session" -ADD COLUMN "total_completion_tokens" integer NOT NULL DEFAULT 0; - -ALTER TABLE "table_agent_session" -ADD COLUMN "total_tokens" integer NOT NULL DEFAULT 0; - --- Agent message: per-message token tracking -ALTER TABLE "table_agent_message" -ADD COLUMN "prompt_tokens" integer; - -ALTER TABLE "table_agent_message" -ADD COLUMN "completion_tokens" integer; - -ALTER TABLE "table_agent_message" -ADD COLUMN "total_tokens" integer; - --- Agent message: subagent support -ALTER TABLE "table_agent_message" -ADD COLUMN "subagent_role" text; - -ALTER TABLE "table_agent_message" -ADD COLUMN "subagent_depth" integer NOT NULL DEFAULT 0; - -ALTER TABLE "table_agent_message" -ADD COLUMN "id_parent_agent_message" text; - --- Foreign key for parent/child relationship -ALTER TABLE "table_agent_message" -ADD CONSTRAINT "table_agent_message_id_parent_agent_message_table_agent_message_id_fk" -FOREIGN KEY ("id_parent_agent_message") REFERENCES "table_agent_message"("id") -ON DELETE CASCADE ON UPDATE CASCADE; - --- Index for parent message lookups -CREATE INDEX "table_agent_message_id_parent_agent_message_index" -ON "table_agent_message" ("id_parent_agent_message"); diff --git a/packages/tools/drizzle/0002_add_agent_message_references.sql b/packages/tools/drizzle/0002_add_agent_message_references.sql deleted file mode 100644 index 3c78dde9..00000000 --- a/packages/tools/drizzle/0002_add_agent_message_references.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add references JSONB column to agent messages for @ mention support -ALTER TABLE "table_agent_message" -ADD COLUMN "references" jsonb; diff --git a/packages/tools/drizzle/0003_rename_content_to_output_and_add_input.sql b/packages/tools/drizzle/0003_rename_content_to_output_and_add_input.sql deleted file mode 100644 index 72c37602..00000000 --- a/packages/tools/drizzle/0003_rename_content_to_output_and_add_input.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Agent message schema refactor: --- - rename assistant response column from content -> output --- - allow nullable user_message for delegated/subagent rows --- - store serialized LLM request payload in input - -ALTER TABLE "table_agent_message" -RENAME COLUMN "content" TO "output"; - -ALTER TABLE "table_agent_message" -ADD COLUMN "input" text; - -ALTER TABLE "table_agent_message" -ALTER COLUMN "user_message" DROP NOT NULL; diff --git a/packages/tools/drizzle/0004_remove_subagent_role_and_rename_depth.sql b/packages/tools/drizzle/0004_remove_subagent_role_and_rename_depth.sql deleted file mode 100644 index 6d277080..00000000 --- a/packages/tools/drizzle/0004_remove_subagent_role_and_rename_depth.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Agent message subagent metadata cleanup: --- - remove unused subagent_role --- - rename subagent_depth -> depth - -ALTER TABLE "table_agent_message" -DROP COLUMN "subagent_role"; - -ALTER TABLE "table_agent_message" -RENAME COLUMN "subagent_depth" TO "depth"; diff --git a/packages/tools/drizzle/0005_add_subscription_and_invoice_tables.sql b/packages/tools/drizzle/0005_add_subscription_and_invoice_tables.sql deleted file mode 100644 index c6fcd8b1..00000000 --- a/packages/tools/drizzle/0005_add_subscription_and_invoice_tables.sql +++ /dev/null @@ -1,54 +0,0 @@ --- Add flexible subscription and invoice tables --- Also add per-org resource limits to organization - --- New enums -CREATE TYPE "enum_organization_subscription_status" AS ENUM ('active', 'cancelled'); -CREATE TYPE "enum_organization_subscription_type" AS ENUM ('support', 'storage_gb', 'agent_tokens_million', 'ocr_pages_hundred'); -CREATE TYPE "enum_invoice_status" AS ENUM ('draft', 'generated', 'paid'); - --- New table: organization subscriptions (replaces single mollieSubscriptionId on org) -CREATE TABLE "table_organization_subscription" ( - "id" text PRIMARY KEY NOT NULL, - "id_organization" text NOT NULL REFERENCES "table_organization"("id") ON DELETE CASCADE ON UPDATE CASCADE, - "type" "enum_organization_subscription_type" NOT NULL, - "quantity" integer NOT NULL DEFAULT 1, - "amount_in_cents" integer NOT NULL, - "mollie_subscription_id" text, - "status" "enum_organization_subscription_status" NOT NULL DEFAULT 'active', - "starts_at" timestamp NOT NULL, - "ends_at" timestamp, - "created_at" timestamp NOT NULL, - "last_updated_at" timestamp, - "created_by" text REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE, - "last_updated_by" text REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE -); - -CREATE INDEX ON "table_organization_subscription" ("id_organization"); - --- New table: invoices (generated monthly as PDF) -CREATE TABLE "table_invoice" ( - "id" text PRIMARY KEY NOT NULL, - "id_organization" text NOT NULL REFERENCES "table_organization"("id") ON DELETE CASCADE ON UPDATE CASCADE, - "invoice_number" text NOT NULL, - "period_start" timestamp NOT NULL, - "period_end" timestamp NOT NULL, - "amount_in_cents" integer NOT NULL, - "currency" varchar(3) NOT NULL DEFAULT 'EUR', - "storage_key" text, - "status" "enum_invoice_status" NOT NULL DEFAULT 'draft', - "created_at" timestamp NOT NULL, - "last_updated_at" timestamp -); - -CREATE INDEX ON "table_invoice" ("id_organization"); - --- Add per-org resource limits to organization -ALTER TABLE "table_organization" -ADD COLUMN "ocr_monthly_limit" integer NOT NULL DEFAULT 100; - -ALTER TABLE "table_organization" -ADD COLUMN "agent_tokens_monthly_limit" integer NOT NULL DEFAULT 1000000; - --- Link payments to invoices -ALTER TABLE "table_organization_payment" -ADD COLUMN "id_invoice" text REFERENCES "table_invoice"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/tools/drizzle/0006_add_organization_billing_fields.sql b/packages/tools/drizzle/0006_add_organization_billing_fields.sql deleted file mode 100644 index 412e178e..00000000 --- a/packages/tools/drizzle/0006_add_organization_billing_fields.sql +++ /dev/null @@ -1,35 +0,0 @@ -ALTER TABLE "table_organization" -ADD COLUMN "licence_amount" integer NOT NULL DEFAULT 0; - -ALTER TABLE "table_organization" -ADD COLUMN "storage_max_usage" integer NOT NULL DEFAULT 1073741824; - -ALTER TABLE "table_organization" -ADD COLUMN "ocr_pages_total_left" integer NOT NULL DEFAULT 100; - -ALTER TABLE "table_organization" -ADD COLUMN "ocr_pages_total_used" integer NOT NULL DEFAULT 0; - -ALTER TABLE "table_organization" -ADD COLUMN "tokens_total_left" integer NOT NULL DEFAULT 1000000; - -ALTER TABLE "table_organization" -ADD COLUMN "tokens_total_used" integer NOT NULL DEFAULT 0; - -UPDATE "table_organization" -SET - "licence_amount" = COALESCE( - ( - SELECT SUM(subscription."amount_in_cents") - FROM "table_organization_subscription" AS subscription - WHERE subscription."id_organization" = "table_organization"."id" - AND subscription."type" = 'support' - AND subscription."status" = 'active' - ), - 0 - ), - "storage_max_usage" = "storage_limit", - "ocr_pages_total_left" = GREATEST("ocr_monthly_limit" - "ocr_current_month_pages_usage", 0), - "ocr_pages_total_used" = "ocr_current_month_pages_usage", - "tokens_total_left" = GREATEST("agent_tokens_monthly_limit" - "agent_tokens_current_month_usage", 0), - "tokens_total_used" = "agent_tokens_current_month_usage"; \ No newline at end of file diff --git a/packages/tools/drizzle/0007_add_organization_payment_service_type.sql b/packages/tools/drizzle/0007_add_organization_payment_service_type.sql deleted file mode 100644 index 956da926..00000000 --- a/packages/tools/drizzle/0007_add_organization_payment_service_type.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "table_organization_payment" -ADD COLUMN "service_type" varchar(32); \ No newline at end of file diff --git a/packages/tools/drizzle/0008_expand_organization_storage_columns.sql b/packages/tools/drizzle/0008_expand_organization_storage_columns.sql deleted file mode 100644 index ab5e46c7..00000000 --- a/packages/tools/drizzle/0008_expand_organization_storage_columns.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE "table_organization" -ALTER COLUMN "storage_limit" TYPE bigint; - -ALTER TABLE "table_organization" -ALTER COLUMN "storage_max_usage" TYPE bigint; - -ALTER TABLE "table_organization" -ALTER COLUMN "storage_current_usage" TYPE bigint; \ No newline at end of file diff --git a/packages/tools/drizzle/0009_add_pending_subscription_columns.sql b/packages/tools/drizzle/0009_add_pending_subscription_columns.sql deleted file mode 100644 index 61841b2f..00000000 --- a/packages/tools/drizzle/0009_add_pending_subscription_columns.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "table_organization" ADD COLUMN "pending_licence_amount" integer; -ALTER TABLE "table_organization" ADD COLUMN "pending_storage_max_usage" bigint; diff --git a/packages/tools/drizzle/0010_add_dashboard_user_super_admin.sql b/packages/tools/drizzle/0010_add_dashboard_user_super_admin.sql deleted file mode 100644 index 9c414bda..00000000 --- a/packages/tools/drizzle/0010_add_dashboard_user_super_admin.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "table_user" ADD COLUMN "is_super_admin" boolean DEFAULT false NOT NULL; diff --git a/packages/tools/drizzle/0011_add_payment_ht_tva_fields.sql b/packages/tools/drizzle/0011_add_payment_ht_tva_fields.sql deleted file mode 100644 index 2c32e571..00000000 --- a/packages/tools/drizzle/0011_add_payment_ht_tva_fields.sql +++ /dev/null @@ -1,11 +0,0 @@ -ALTER TABLE "table_organization_payment" -ADD COLUMN "amount_ht_in_cents" integer NOT NULL DEFAULT 0, -ADD COLUMN "amount_tva_in_cents" integer NOT NULL DEFAULT 0; - -UPDATE "table_organization_payment" -SET - "amount_ht_in_cents" = "amount_in_cents", - "amount_tva_in_cents" = CASE - WHEN "category" IN ('subscription', 'wallet_spending') THEN ROUND("amount_in_cents" * 0.20) - ELSE 0 - END; \ No newline at end of file diff --git a/packages/tools/drizzle/0012_rename_user_tables_and_drop_admin_tables.sql b/packages/tools/drizzle/0012_rename_user_tables_and_drop_admin_tables.sql deleted file mode 100644 index be3d1b0e..00000000 --- a/packages/tools/drizzle/0012_rename_user_tables_and_drop_admin_tables.sql +++ /dev/null @@ -1,12 +0,0 @@ -ALTER TABLE IF EXISTS "table_dashboard_user" RENAME TO "table_user"; -ALTER TABLE IF EXISTS "table_dashboard_user_session" RENAME TO "table_user_session"; - -ALTER TABLE IF EXISTS "table_ticket_message" -DROP CONSTRAINT IF EXISTS "table_ticket_message_id_admin_user_table_admin_user_id_fk"; - -ALTER TABLE IF EXISTS "table_ticket_message" -ADD CONSTRAINT "table_ticket_message_id_admin_user_table_user_id_fk" -FOREIGN KEY ("id_admin_user") REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE; - -DROP TABLE IF EXISTS "table_admin_user_session"; -DROP TABLE IF EXISTS "table_admin_user"; diff --git a/packages/tools/drizzle/0013_add_payment_quantity_unit_amount_fields.sql b/packages/tools/drizzle/0013_add_payment_quantity_unit_amount_fields.sql deleted file mode 100644 index ed25d4c4..00000000 --- a/packages/tools/drizzle/0013_add_payment_quantity_unit_amount_fields.sql +++ /dev/null @@ -1,15 +0,0 @@ -ALTER TABLE "table_organization_payment" -ADD COLUMN "quantity" integer NOT NULL DEFAULT 1, -ADD COLUMN "unit_amount_in_cents" integer NOT NULL DEFAULT 0; - -UPDATE "table_organization_payment" -SET - "quantity" = CASE - WHEN "service_type" = 'ocr_pages_hundred' AND "amount_in_cents" > 0 THEN "amount_in_cents" - ELSE 1 - END, - "unit_amount_in_cents" = CASE - WHEN "service_type" = 'ocr_pages_hundred' AND "amount_in_cents" > 0 THEN 1 - WHEN "amount_in_cents" > 0 THEN "amount_in_cents" - ELSE 0 - END; \ No newline at end of file diff --git a/packages/tools/drizzle/0014_make_payment_invoice_non_nullable.sql b/packages/tools/drizzle/0014_make_payment_invoice_non_nullable.sql deleted file mode 100644 index 4cd3668a..00000000 --- a/packages/tools/drizzle/0014_make_payment_invoice_non_nullable.sql +++ /dev/null @@ -1,78 +0,0 @@ -WITH payments_without_invoice AS ( - SELECT - p.id, - p.id_organization, - date_trunc('month', COALESCE(p.period_start, p.paid_at, p.created_at)) AS month_start - FROM "table_organization_payment" p - WHERE p.id_invoice IS NULL -), -invoice_candidates AS ( - SELECT - pwi.id_organization, - pwi.month_start, - (date_trunc('month', pwi.month_start) + INTERVAL '1 month - 1 millisecond') AS month_end, - SUM(p.amount_in_cents)::integer AS amount_in_cents, - 'MIGR-' || upper(substr(md5(pwi.id_organization || pwi.month_start::text), 1, 8)) AS reference - FROM payments_without_invoice pwi - INNER JOIN "table_organization_payment" p ON p.id = pwi.id - GROUP BY pwi.id_organization, pwi.month_start -), -inserted_invoices AS ( - INSERT INTO "table_invoice" ( - "id", - "id_organization", - "invoice_number", - "period_start", - "period_end", - "amount_in_cents", - "currency", - "storage_key", - "status", - "created_at", - "last_updated_at" - ) - SELECT - 'inv_migr_' || lower(replace(substr(md5(ic.id_organization || ic.month_start::text || random()::text), 1, 20), '-', '')), - ic.id_organization, - ic.reference, - ic.month_start, - ic.month_end, - ic.amount_in_cents, - 'EUR', - NULL, - 'draft', - NOW(), - NULL - FROM invoice_candidates ic - WHERE NOT EXISTS ( - SELECT 1 - FROM "table_invoice" i - WHERE - i.id_organization = ic.id_organization - AND date_trunc('month', i.period_start) = ic.month_start - ) -) -UPDATE "table_organization_payment" p -SET "id_invoice" = i.id -FROM ( - SELECT - min(i.id) AS id, - i.id_organization, - date_trunc('month', i.period_start) AS month_start - FROM "table_invoice" i - GROUP BY i.id_organization, date_trunc('month', i.period_start) -) i -WHERE - p.id_invoice IS NULL - AND p.id_organization = i.id_organization - AND date_trunc('month', COALESCE(p.period_start, p.paid_at, p.created_at)) = i.month_start; - -ALTER TABLE "table_organization_payment" -DROP CONSTRAINT IF EXISTS "table_organization_payment_id_invoice_table_invoice_id_fk"; - -ALTER TABLE "table_organization_payment" -ALTER COLUMN "id_invoice" SET NOT NULL; - -ALTER TABLE "table_organization_payment" -ADD CONSTRAINT "table_organization_payment_id_invoice_table_invoice_id_fk" -FOREIGN KEY ("id_invoice") REFERENCES "table_invoice"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/tools/drizzle/0015_payment_ht_column_cleanup.sql b/packages/tools/drizzle/0015_payment_ht_column_cleanup.sql deleted file mode 100644 index b1b88e31..00000000 --- a/packages/tools/drizzle/0015_payment_ht_column_cleanup.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE "table_organization_payment" -DROP COLUMN IF EXISTS "mollie_subscription_id"; - -ALTER TABLE "table_organization_payment" -DROP COLUMN IF EXISTS "amount_in_cents"; - -ALTER TABLE "table_organization_payment" -RENAME COLUMN "unit_amount_in_cents" TO "unit_amount_ht_in_cents"; diff --git a/packages/tools/drizzle/0016_remove_sql_enums.sql b/packages/tools/drizzle/0016_remove_sql_enums.sql deleted file mode 100644 index 42eba8e6..00000000 --- a/packages/tools/drizzle/0016_remove_sql_enums.sql +++ /dev/null @@ -1,60 +0,0 @@ -ALTER TABLE "table_account" -ALTER COLUMN "balance_sheet_asset_column" TYPE varchar(32) USING "balance_sheet_asset_column"::text, -ALTER COLUMN "balance_sheet_asset_flow" TYPE varchar(32) USING "balance_sheet_asset_flow"::text, -ALTER COLUMN "balance_sheet_liability_column" TYPE varchar(32) USING "balance_sheet_liability_column"::text, -ALTER COLUMN "balance_sheet_liability_flow" TYPE varchar(32) USING "balance_sheet_liability_flow"::text, -ALTER COLUMN "type" TYPE varchar(16) USING "type"::text; - -ALTER TABLE "table_agent_message" -ALTER COLUMN "state" TYPE varchar(16) USING "state"::text; - -ALTER TABLE "table_balance_sheet" -ALTER COLUMN "side" TYPE varchar(16) USING "side"::text; - -ALTER TABLE "table_computation_income_statement" -ALTER COLUMN "operation" TYPE varchar(16) USING "operation"::text; - -ALTER TABLE "table_document" -ALTER COLUMN "type" TYPE varchar(64) USING "type"::text; - -ALTER TABLE "table_invoice" -ALTER COLUMN "status" TYPE varchar(16) USING "status"::text; - -ALTER TABLE "table_organization" -ALTER COLUMN "scope" TYPE varchar(32) USING "scope"::text; - -ALTER TABLE "table_organization_payment" -ALTER COLUMN "category" TYPE varchar(32) USING "category"::text, -ALTER COLUMN "status" TYPE varchar(16) USING "status"::text; - -ALTER TABLE "table_organization_subscription" -ALTER COLUMN "type" TYPE varchar(32) USING "type"::text, -ALTER COLUMN "status" TYPE varchar(16) USING "status"::text; - -ALTER TABLE "table_organization_user" -ALTER COLUMN "status" TYPE varchar(16) USING "status"::text; - -ALTER TABLE "table_ticket" -ALTER COLUMN "category" TYPE varchar(32) USING "category"::text, -ALTER COLUMN "status" TYPE varchar(16) USING "status"::text; - -ALTER TABLE "table_worker_job" -ALTER COLUMN "status" TYPE varchar(16) USING "status"::text; - -DROP TYPE IF EXISTS "enum_account_balance_sheet_flow"; -DROP TYPE IF EXISTS "enum_account_balance_sheet_column"; -DROP TYPE IF EXISTS "enum_account_type"; -DROP TYPE IF EXISTS "enum_agent_message_state"; -DROP TYPE IF EXISTS "enum_balance_sheet_side"; -DROP TYPE IF EXISTS "enum_computation_incomeStatement_operation"; -DROP TYPE IF EXISTS "enum_document_type"; -DROP TYPE IF EXISTS "enum_invoice_status"; -DROP TYPE IF EXISTS "enum_organization_scope"; -DROP TYPE IF EXISTS "enum_organization_payment_status"; -DROP TYPE IF EXISTS "enum_organization_payment_category"; -DROP TYPE IF EXISTS "enum_organization_subscription_status"; -DROP TYPE IF EXISTS "enum_organization_subscription_type"; -DROP TYPE IF EXISTS "enum_organization_user_status"; -DROP TYPE IF EXISTS "enum_ticket_status"; -DROP TYPE IF EXISTS "enum_ticket_type"; -DROP TYPE IF EXISTS "enum_worker_job_status"; diff --git a/packages/tools/drizzle/0017_add_organization_payment_flow.sql b/packages/tools/drizzle/0017_add_organization_payment_flow.sql deleted file mode 100644 index 58934765..00000000 --- a/packages/tools/drizzle/0017_add_organization_payment_flow.sql +++ /dev/null @@ -1,12 +0,0 @@ -ALTER TABLE "table_organization_payment" -ADD COLUMN IF NOT EXISTS "flow" varchar(16); - -UPDATE "table_organization_payment" -SET "flow" = CASE - WHEN "category" IN ('top_up', 'setup') THEN 'debit' - ELSE 'credit' -END -WHERE "flow" IS NULL; - -ALTER TABLE "table_organization_payment" -ALTER COLUMN "flow" SET NOT NULL; diff --git a/packages/tools/drizzle/0018_schema_cleanup.sql b/packages/tools/drizzle/0018_schema_cleanup.sql deleted file mode 100644 index 18ff3795..00000000 --- a/packages/tools/drizzle/0018_schema_cleanup.sql +++ /dev/null @@ -1,26 +0,0 @@ --- Account: rename is_mandatory → is_optional (flip boolean), drop is_class -ALTER TABLE table_account RENAME COLUMN is_mandatory TO is_optional; -UPDATE table_account SET is_optional = NOT is_optional; -ALTER TABLE table_account DROP COLUMN is_class; - --- Agent session: rename prompt/completion tokens, drop total_tokens -ALTER TABLE table_agent_session RENAME COLUMN total_prompt_tokens TO total_input_tokens; -ALTER TABLE table_agent_session RENAME COLUMN total_completion_tokens TO total_output_tokens; -ALTER TABLE table_agent_session DROP COLUMN total_tokens; - --- Agent message: rename prompt/completion tokens, drop total_tokens -ALTER TABLE table_agent_message RENAME COLUMN prompt_tokens TO input_tokens; -ALTER TABLE table_agent_message RENAME COLUMN completion_tokens TO output_tokens; -ALTER TABLE table_agent_message DROP COLUMN total_tokens; - --- Invoice: rename invoice_number → reference, period_start → starting_at, period_end → ending_at -ALTER TABLE table_invoice RENAME COLUMN invoice_number TO reference; -ALTER TABLE table_invoice RENAME COLUMN period_start TO starting_at; -ALTER TABLE table_invoice RENAME COLUMN period_end TO ending_at; - --- Organization: drop Mollie subscription fields -ALTER TABLE table_organization DROP COLUMN mollie_subscription_id; -ALTER TABLE table_organization DROP COLUMN subscription_ending_at; - --- Drop unused document table -DROP TABLE IF EXISTS table_document; diff --git a/packages/tools/drizzle/0019_drop_worker_job_table.sql b/packages/tools/drizzle/0019_drop_worker_job_table.sql deleted file mode 100644 index a664e8ee..00000000 --- a/packages/tools/drizzle/0019_drop_worker_job_table.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS "table_worker_job"; diff --git a/packages/tools/drizzle/0020_file_hash_unique_by_org_year.sql b/packages/tools/drizzle/0020_file_hash_unique_by_org_year.sql deleted file mode 100644 index 4c5d5d49..00000000 --- a/packages/tools/drizzle/0020_file_hash_unique_by_org_year.sql +++ /dev/null @@ -1,6 +0,0 @@ -ALTER TABLE "table_file" -DROP CONSTRAINT IF EXISTS "table_file_hash_unique"; - -CREATE UNIQUE INDEX IF NOT EXISTS "table_file_id_organization_id_year_hash_unique" -ON "table_file" ("id_organization", "id_year", "hash") -WHERE "hash" IS NOT NULL; diff --git a/packages/tools/drizzle/0021_refactor_organization_usage_and_pending_fields.sql b/packages/tools/drizzle/0021_refactor_organization_usage_and_pending_fields.sql deleted file mode 100644 index 4adc2c62..00000000 --- a/packages/tools/drizzle/0021_refactor_organization_usage_and_pending_fields.sql +++ /dev/null @@ -1,29 +0,0 @@ -ALTER TABLE "table_organization" -RENAME COLUMN "pending_licence_amount" TO "licence_amount_pending"; - -ALTER TABLE "table_organization" -RENAME COLUMN "pending_storage_max_usage" TO "storage_limit_pending"; - -ALTER TABLE "table_organization" -RENAME COLUMN "ocr_pages_total_left" TO "ocr_pages_total_available"; - -ALTER TABLE "table_organization" -RENAME COLUMN "tokens_total_left" TO "tokens_total_available"; - -ALTER TABLE "table_organization" -DROP COLUMN IF EXISTS "storage_max_usage"; - -ALTER TABLE "table_organization" -DROP COLUMN IF EXISTS "usage_month_start_at"; - -ALTER TABLE "table_organization" -DROP COLUMN IF EXISTS "ocr_current_month_pages_usage"; - -ALTER TABLE "table_organization" -DROP COLUMN IF EXISTS "agent_tokens_current_month_usage"; - -ALTER TABLE "table_organization" -DROP COLUMN IF EXISTS "ocr_monthly_limit"; - -ALTER TABLE "table_organization" -DROP COLUMN IF EXISTS "agent_tokens_monthly_limit"; diff --git a/packages/tools/drizzle/0022_move_storage_to_org_level.sql b/packages/tools/drizzle/0022_move_storage_to_org_level.sql deleted file mode 100644 index c5afa3d3..00000000 --- a/packages/tools/drizzle/0022_move_storage_to_org_level.sql +++ /dev/null @@ -1,17 +0,0 @@ --- Move file/folder storage from year-scoped to organization-level --- 1. Drop id_year from table_file and table_folder --- 2. Update deduplication index to org+hash scope - --- Step 1: Drop id_year column from table_file -ALTER TABLE "table_file" DROP COLUMN IF EXISTS "id_year"; - --- Step 2: Drop id_year column from table_folder -ALTER TABLE "table_folder" DROP COLUMN IF EXISTS "id_year"; - --- Step 3: Drop old per-year deduplication index -DROP INDEX IF EXISTS "table_file_id_organization_id_year_hash_unique"; - --- Step 4: Create new org-level deduplication index -CREATE UNIQUE INDEX IF NOT EXISTS "table_file_id_organization_hash_unique" -ON "table_file" ("id_organization", "hash") -WHERE "hash" IS NOT NULL; diff --git a/packages/tools/drizzle/0023_add_date_to_file.sql b/packages/tools/drizzle/0023_add_date_to_file.sql deleted file mode 100644 index 39330cf5..00000000 --- a/packages/tools/drizzle/0023_add_date_to_file.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Add optional content date to table_file (e.g. invoice date, distinct from createdAt) -ALTER TABLE "table_file" ADD COLUMN "date" TIMESTAMP WITH TIME ZONE; diff --git a/packages/tools/drizzle/0024_make_file_name_not_null.sql b/packages/tools/drizzle/0024_make_file_name_not_null.sql deleted file mode 100644 index 0e7aa717..00000000 --- a/packages/tools/drizzle/0024_make_file_name_not_null.sql +++ /dev/null @@ -1,2 +0,0 @@ -UPDATE "table_file" SET "name" = '' WHERE "name" IS NULL; -ALTER TABLE "table_file" ALTER COLUMN "name" SET NOT NULL; diff --git a/packages/tools/drizzle/0025_byok_columns.sql b/packages/tools/drizzle/0025_byok_columns.sql deleted file mode 100644 index d35d324e..00000000 --- a/packages/tools/drizzle/0025_byok_columns.sql +++ /dev/null @@ -1,39 +0,0 @@ --- Drop billing tables -DROP TABLE IF EXISTS table_organization_payment; -DROP TABLE IF EXISTS table_organization_subscription; -DROP TABLE IF EXISTS table_invoice; - --- Remove billing columns from table_organization -ALTER TABLE table_organization - DROP COLUMN IF EXISTS siren, - DROP COLUMN IF EXISTS email, - DROP COLUMN IF EXISTS mollie_customer_id, - DROP COLUMN IF EXISTS licence_amount, - DROP COLUMN IF EXISTS licence_amount_pending, - DROP COLUMN IF EXISTS storage_limit_pending, - DROP COLUMN IF EXISTS wallet_balance_in_cents, - DROP COLUMN IF EXISTS ocr_pages_total_available, - DROP COLUMN IF EXISTS ocr_pages_total_used, - DROP COLUMN IF EXISTS tokens_total_available, - DROP COLUMN IF EXISTS tokens_total_used; - --- Add BYOK storage columns to table_organization -ALTER TABLE table_organization - ADD COLUMN storage_endpoint text, - ADD COLUMN storage_access_key text, - ADD COLUMN storage_secret_key text, - ADD COLUMN storage_bucket_name text, - ADD COLUMN storage_region varchar(64); - --- Add BYOK LLM/OCR columns to table_user -ALTER TABLE table_user - ADD COLUMN llm_api_key text, - ADD COLUMN llm_base_url text, - ADD COLUMN llm_model varchar(128), - ADD COLUMN ocr_endpoint text, - ADD COLUMN ocr_api_key text, - ADD COLUMN ocr_model varchar(128); - --- Remove legacy llm_provider column (no longer needed — provider is determined by URL) -ALTER TABLE table_user - DROP COLUMN IF EXISTS llm_provider; diff --git a/packages/tools/drizzle/0026_create_inventory_tables.sql b/packages/tools/drizzle/0026_create_inventory_tables.sql deleted file mode 100644 index 6096c2b3..00000000 --- a/packages/tools/drizzle/0026_create_inventory_tables.sql +++ /dev/null @@ -1,49 +0,0 @@ --- Create inventory tables for stock tracking per organization and year - -CREATE TABLE "table_inventory_item" ( - "id" text PRIMARY KEY NOT NULL, - "id_organization" text NOT NULL REFERENCES "table_organization"("id") ON DELETE CASCADE ON UPDATE CASCADE, - "id_year" text NOT NULL REFERENCES "table_year"("id") ON DELETE CASCADE ON UPDATE CASCADE, - - "sku" varchar(64), - "name" varchar(256) NOT NULL, - "description" varchar(1024), - "category" varchar(256), - "unit" varchar(32) NOT NULL, - "unit_price" numeric(10, 2), - "current_quantity" numeric(10, 2) NOT NULL DEFAULT 0, - "minimum_threshold" numeric(10, 2), - "location" varchar(256), - - "created_at" timestamp NOT NULL, - "last_updated_at" timestamp, - "created_by" text REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE, - "last_updated_by" text REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE, - - UNIQUE ("id_organization", "id_year", "sku") -); - -CREATE INDEX ON "table_inventory_item" ("id_organization", "id_year"); -CREATE INDEX ON "table_inventory_item" ("id_organization", "id_year", "category"); - -CREATE TABLE "table_inventory_movement" ( - "id" text PRIMARY KEY NOT NULL, - "id_organization" text NOT NULL REFERENCES "table_organization"("id") ON DELETE CASCADE ON UPDATE CASCADE, - "id_year" text NOT NULL REFERENCES "table_year"("id") ON DELETE CASCADE ON UPDATE CASCADE, - "id_inventory_item" text NOT NULL REFERENCES "table_inventory_item"("id") ON DELETE CASCADE ON UPDATE CASCADE, - - "quantity_change" numeric(10, 2) NOT NULL, - "unit_price_at_movement" numeric(10, 2), - "reference" varchar(256), - "reason" varchar(256), - "movement_date" timestamp NOT NULL, - - "created_at" timestamp NOT NULL, - "last_updated_at" timestamp, - "created_by" text REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE, - "last_updated_by" text REFERENCES "table_user"("id") ON DELETE SET NULL ON UPDATE CASCADE -); - -CREATE INDEX ON "table_inventory_movement" ("id_organization", "id_year"); -CREATE INDEX ON "table_inventory_movement" ("id_inventory_item"); -CREATE INDEX ON "table_inventory_movement" ("id_inventory_item", "movement_date"); diff --git a/packages/tools/drizzle/0027_remove_ticket_and_superadmin.sql b/packages/tools/drizzle/0027_remove_ticket_and_superadmin.sql deleted file mode 100644 index 61785466..00000000 --- a/packages/tools/drizzle/0027_remove_ticket_and_superadmin.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Drop ticket tables and superadmin column (feature removed in code) -DROP TABLE IF EXISTS "table_ticket_message"; -DROP TABLE IF EXISTS "table_ticket"; - -ALTER TABLE "table_user" DROP COLUMN IF EXISTS "is_super_admin"; diff --git a/packages/tools/drizzle/0028_remove_agent_and_make_api_key_user_scoped.sql b/packages/tools/drizzle/0028_remove_agent_and_make_api_key_user_scoped.sql deleted file mode 100644 index 3d42a72e..00000000 --- a/packages/tools/drizzle/0028_remove_agent_and_make_api_key_user_scoped.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Drop agent tables and LLM columns (agent feature removed in code) -DROP TABLE IF EXISTS "table_agent_message"; -DROP TABLE IF EXISTS "table_agent_session"; - -ALTER TABLE "table_user" - DROP COLUMN IF EXISTS "llm_api_key", - DROP COLUMN IF EXISTS "llm_base_url", - DROP COLUMN IF EXISTS "llm_model"; - --- Make API keys user-scoped (nullable organization for personal keys) -ALTER TABLE "table_api_key" - ALTER COLUMN "id_organization" DROP NOT NULL; diff --git a/packages/tools/drizzle/0029_remove_email_validation_columns.sql b/packages/tools/drizzle/0029_remove_email_validation_columns.sql deleted file mode 100644 index c4963246..00000000 --- a/packages/tools/drizzle/0029_remove_email_validation_columns.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Migration 0029: Remove email validation columns from table_user --- Email validation is no longer used - sign-up is immediate - -ALTER TABLE table_user -DROP COLUMN IF EXISTS is_email_validated, -DROP COLUMN IF EXISTS email_to_validate, -DROP COLUMN IF EXISTS email_token, -DROP COLUMN IF EXISTS email_token_expires_at; diff --git a/packages/tools/drizzle/0030_remove_api_key_table.sql b/packages/tools/drizzle/0030_remove_api_key_table.sql deleted file mode 100644 index 01359f30..00000000 --- a/packages/tools/drizzle/0030_remove_api_key_table.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Migration 0030: Remove API key system --- Authentication is now cookie-only for all interfaces (Dashboard, API, CLI) - -DROP TABLE IF EXISTS table_api_key; diff --git a/packages/tools/drizzle/0031_remove_user_ocr_columns.sql b/packages/tools/drizzle/0031_remove_user_ocr_columns.sql deleted file mode 100644 index ccd1cf42..00000000 --- a/packages/tools/drizzle/0031_remove_user_ocr_columns.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Remove per-user BYOK OCR credentials (now provided via environment variables) -ALTER TABLE table_user - DROP COLUMN IF EXISTS ocr_endpoint, - DROP COLUMN IF EXISTS ocr_api_key, - DROP COLUMN IF EXISTS ocr_model; diff --git a/packages/tools/drizzle/meta/_journal.json b/packages/tools/drizzle/meta/_journal.json deleted file mode 100644 index 99263a05..00000000 --- a/packages/tools/drizzle/meta/_journal.json +++ /dev/null @@ -1 +0,0 @@ -{ "version": "7", "dialect": "postgresql", "entries": [] } diff --git a/packages/tools/drizzle/meta/_snapshot.json b/packages/tools/drizzle/meta/_snapshot.json new file mode 100644 index 00000000..401983f4 --- /dev/null +++ b/packages/tools/drizzle/meta/_snapshot.json @@ -0,0 +1,2956 @@ +{ + "version": "7", + "dialect": "postgresql", + "tables": { + "public.table_account": { + "name": "table_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_account_parent": { + "name": "id_account_parent", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "id_balance_sheet_asset": { + "name": "id_balance_sheet_asset", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "balance_sheet_asset_column": { + "name": "balance_sheet_asset_column", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "balance_sheet_asset_flow": { + "name": "balance_sheet_asset_flow", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "id_balance_sheet_liability": { + "name": "id_balance_sheet_liability", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "balance_sheet_liability_column": { + "name": "balance_sheet_liability_column", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "balance_sheet_liability_flow": { + "name": "balance_sheet_liability_flow", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "id_income_statement": { + "name": "id_income_statement", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "is_optional": { + "name": "is_optional", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_selectable": { + "name": "is_selectable", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_account_id_organization_table_organization_id_fk": { + "name": "table_account_id_organization_table_organization_id_fk", + "tableFrom": "table_account", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_account_id_year_table_year_id_fk": { + "name": "table_account_id_year_table_year_id_fk", + "tableFrom": "table_account", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_account_id_account_parent_table_account_id_fk": { + "name": "table_account_id_account_parent_table_account_id_fk", + "tableFrom": "table_account", + "tableTo": "table_account", + "columnsFrom": [ + "id_account_parent" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_account_id_balance_sheet_asset_table_balance_sheet_id_fk": { + "name": "table_account_id_balance_sheet_asset_table_balance_sheet_id_fk", + "tableFrom": "table_account", + "tableTo": "table_balance_sheet", + "columnsFrom": [ + "id_balance_sheet_asset" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_account_id_balance_sheet_liability_table_balance_sheet_id_fk": { + "name": "table_account_id_balance_sheet_liability_table_balance_sheet_id_fk", + "tableFrom": "table_account", + "tableTo": "table_balance_sheet", + "columnsFrom": [ + "id_balance_sheet_liability" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_account_id_income_statement_table_income_statement_id_fk": { + "name": "table_account_id_income_statement_table_income_statement_id_fk", + "tableFrom": "table_account", + "tableTo": "table_income_statement", + "columnsFrom": [ + "id_income_statement" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_account_created_by_table_user_id_fk": { + "name": "table_account_created_by_table_user_id_fk", + "tableFrom": "table_account", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_account_last_updated_by_table_user_id_fk": { + "name": "table_account_last_updated_by_table_user_id_fk", + "tableFrom": "table_account", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_account_id_organization_id_year_number_unique": { + "name": "table_account_id_organization_id_year_number_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_file": { + "name": "table_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_folder": { + "name": "id_folder", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "reference": { + "name": "reference", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_file_id_organization_hash_unique": { + "name": "table_file_id_organization_hash_unique", + "columns": [ + { + "expression": "id_organization", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_file\".\"hash\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_file_id_organization_table_organization_id_fk": { + "name": "table_file_id_organization_table_organization_id_fk", + "tableFrom": "table_file", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_file_id_folder_table_folder_id_fk": { + "name": "table_file_id_folder_table_folder_id_fk", + "tableFrom": "table_file", + "tableTo": "table_folder", + "columnsFrom": [ + "id_folder" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_file_created_by_table_user_id_fk": { + "name": "table_file_created_by_table_user_id_fk", + "tableFrom": "table_file", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_file_last_updated_by_table_user_id_fk": { + "name": "table_file_last_updated_by_table_user_id_fk", + "tableFrom": "table_file", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_folder": { + "name": "table_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_folder_parent": { + "name": "id_folder_parent", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_folder_id_organization_table_organization_id_fk": { + "name": "table_folder_id_organization_table_organization_id_fk", + "tableFrom": "table_folder", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_folder_id_folder_parent_table_folder_id_fk": { + "name": "table_folder_id_folder_parent_table_folder_id_fk", + "tableFrom": "table_folder", + "tableTo": "table_folder", + "columnsFrom": [ + "id_folder_parent" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_folder_created_by_table_user_id_fk": { + "name": "table_folder_created_by_table_user_id_fk", + "tableFrom": "table_folder", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_folder_last_updated_by_table_user_id_fk": { + "name": "table_folder_last_updated_by_table_user_id_fk", + "tableFrom": "table_folder", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_balance_sheet": { + "name": "table_balance_sheet", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_balance_sheet_parent": { + "name": "id_balance_sheet_parent", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_total": { + "name": "is_total", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_balance_sheet_id_organization_table_organization_id_fk": { + "name": "table_balance_sheet_id_organization_table_organization_id_fk", + "tableFrom": "table_balance_sheet", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_balance_sheet_id_year_table_year_id_fk": { + "name": "table_balance_sheet_id_year_table_year_id_fk", + "tableFrom": "table_balance_sheet", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_balance_sheet_id_balance_sheet_parent_table_balance_sheet_id_fk": { + "name": "table_balance_sheet_id_balance_sheet_parent_table_balance_sheet_id_fk", + "tableFrom": "table_balance_sheet", + "tableTo": "table_balance_sheet", + "columnsFrom": [ + "id_balance_sheet_parent" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_balance_sheet_created_by_table_user_id_fk": { + "name": "table_balance_sheet_created_by_table_user_id_fk", + "tableFrom": "table_balance_sheet", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_balance_sheet_last_updated_by_table_user_id_fk": { + "name": "table_balance_sheet_last_updated_by_table_user_id_fk", + "tableFrom": "table_balance_sheet", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_balance_sheet_id_organization_id_year_side_number_unique": { + "name": "table_balance_sheet_id_organization_id_year_side_number_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "side", + "number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_computation": { + "name": "table_computation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_computation_id_organization_table_organization_id_fk": { + "name": "table_computation_id_organization_table_organization_id_fk", + "tableFrom": "table_computation", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_computation_id_year_table_year_id_fk": { + "name": "table_computation_id_year_table_year_id_fk", + "tableFrom": "table_computation", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_computation_created_by_table_user_id_fk": { + "name": "table_computation_created_by_table_user_id_fk", + "tableFrom": "table_computation", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_computation_last_updated_by_table_user_id_fk": { + "name": "table_computation_last_updated_by_table_user_id_fk", + "tableFrom": "table_computation", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_computation_id_organization_id_year_number_unique": { + "name": "table_computation_id_organization_id_year_number_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_computation_income_statement": { + "name": "table_computation_income_statement", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_computation": { + "name": "id_computation", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_income_statement": { + "name": "id_income_statement", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_computation_income_statement_id_organization_id_year_index": { + "name": "table_computation_income_statement_id_organization_id_year_index", + "columns": [ + { + "expression": "id_organization", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id_year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_computation_income_statement_id_organization_table_organization_id_fk": { + "name": "table_computation_income_statement_id_organization_table_organization_id_fk", + "tableFrom": "table_computation_income_statement", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_computation_income_statement_id_year_table_year_id_fk": { + "name": "table_computation_income_statement_id_year_table_year_id_fk", + "tableFrom": "table_computation_income_statement", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_computation_income_statement_id_computation_table_computation_id_fk": { + "name": "table_computation_income_statement_id_computation_table_computation_id_fk", + "tableFrom": "table_computation_income_statement", + "tableTo": "table_computation", + "columnsFrom": [ + "id_computation" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_computation_income_statement_id_income_statement_table_income_statement_id_fk": { + "name": "table_computation_income_statement_id_income_statement_table_income_statement_id_fk", + "tableFrom": "table_computation_income_statement", + "tableTo": "table_income_statement", + "columnsFrom": [ + "id_income_statement" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_computation_income_statement_created_by_table_user_id_fk": { + "name": "table_computation_income_statement_created_by_table_user_id_fk", + "tableFrom": "table_computation_income_statement", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_computation_income_statement_last_updated_by_table_user_id_fk": { + "name": "table_computation_income_statement_last_updated_by_table_user_id_fk", + "tableFrom": "table_computation_income_statement", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_computation_income_statement_id_computation_id_income_statement_unique": { + "name": "table_computation_income_statement_id_computation_id_income_statement_unique", + "nullsNotDistinct": false, + "columns": [ + "id_computation", + "id_income_statement" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_entry": { + "name": "table_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_journal": { + "name": "id_journal", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "id_file": { + "name": "id_file", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_entry_id_organization_id_year_index": { + "name": "table_entry_id_organization_id_year_index", + "columns": [ + { + "expression": "id_organization", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id_year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_entry_id_organization_table_organization_id_fk": { + "name": "table_entry_id_organization_table_organization_id_fk", + "tableFrom": "table_entry", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_id_year_table_year_id_fk": { + "name": "table_entry_id_year_table_year_id_fk", + "tableFrom": "table_entry", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_id_journal_table_journal_id_fk": { + "name": "table_entry_id_journal_table_journal_id_fk", + "tableFrom": "table_entry", + "tableTo": "table_journal", + "columnsFrom": [ + "id_journal" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_entry_id_file_table_file_id_fk": { + "name": "table_entry_id_file_table_file_id_fk", + "tableFrom": "table_entry", + "tableTo": "table_file", + "columnsFrom": [ + "id_file" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_entry_created_by_table_user_id_fk": { + "name": "table_entry_created_by_table_user_id_fk", + "tableFrom": "table_entry", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_entry_last_updated_by_table_user_id_fk": { + "name": "table_entry_last_updated_by_table_user_id_fk", + "tableFrom": "table_entry", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_entry_line": { + "name": "table_entry_line", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_entry": { + "name": "id_entry", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_account": { + "name": "id_account", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "is_computed_for_journal_report": { + "name": "is_computed_for_journal_report", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_computed_for_ledger_report": { + "name": "is_computed_for_ledger_report", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_computed_for_balance_report": { + "name": "is_computed_for_balance_report", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_computed_for_balance_sheet_report": { + "name": "is_computed_for_balance_sheet_report", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_computed_for_income_statement_report": { + "name": "is_computed_for_income_statement_report", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "debit": { + "name": "debit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "credit": { + "name": "credit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_entry_line_id_organization_id_year_index": { + "name": "table_entry_line_id_organization_id_year_index", + "columns": [ + { + "expression": "id_organization", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id_year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_entry_line_id_entry_index": { + "name": "table_entry_line_id_entry_index", + "columns": [ + { + "expression": "id_entry", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_entry_line_id_organization_table_organization_id_fk": { + "name": "table_entry_line_id_organization_table_organization_id_fk", + "tableFrom": "table_entry_line", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_line_id_year_table_year_id_fk": { + "name": "table_entry_line_id_year_table_year_id_fk", + "tableFrom": "table_entry_line", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_line_id_entry_table_entry_id_fk": { + "name": "table_entry_line_id_entry_table_entry_id_fk", + "tableFrom": "table_entry_line", + "tableTo": "table_entry", + "columnsFrom": [ + "id_entry" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_line_id_account_table_account_id_fk": { + "name": "table_entry_line_id_account_table_account_id_fk", + "tableFrom": "table_entry_line", + "tableTo": "table_account", + "columnsFrom": [ + "id_account" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_line_created_by_table_user_id_fk": { + "name": "table_entry_line_created_by_table_user_id_fk", + "tableFrom": "table_entry_line", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_entry_line_last_updated_by_table_user_id_fk": { + "name": "table_entry_line_last_updated_by_table_user_id_fk", + "tableFrom": "table_entry_line", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_entry_tag": { + "name": "table_entry_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_entry": { + "name": "id_entry", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_tag": { + "name": "id_tag", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "table_entry_tag_id_organization_id_year_index": { + "name": "table_entry_tag_id_organization_id_year_index", + "columns": [ + { + "expression": "id_organization", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id_year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_entry_tag_id_entry_index": { + "name": "table_entry_tag_id_entry_index", + "columns": [ + { + "expression": "id_entry", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_entry_tag_id_tag_index": { + "name": "table_entry_tag_id_tag_index", + "columns": [ + { + "expression": "id_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_entry_tag_id_organization_table_organization_id_fk": { + "name": "table_entry_tag_id_organization_table_organization_id_fk", + "tableFrom": "table_entry_tag", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_tag_id_year_table_year_id_fk": { + "name": "table_entry_tag_id_year_table_year_id_fk", + "tableFrom": "table_entry_tag", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_tag_id_entry_table_entry_id_fk": { + "name": "table_entry_tag_id_entry_table_entry_id_fk", + "tableFrom": "table_entry_tag", + "tableTo": "table_entry", + "columnsFrom": [ + "id_entry" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_entry_tag_id_tag_table_tag_id_fk": { + "name": "table_entry_tag_id_tag_table_tag_id_fk", + "tableFrom": "table_entry_tag", + "tableTo": "table_tag", + "columnsFrom": [ + "id_tag" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_entry_tag_id_entry_id_tag_unique": { + "name": "table_entry_tag_id_entry_id_tag_unique", + "nullsNotDistinct": false, + "columns": [ + "id_entry", + "id_tag" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_income_statement": { + "name": "table_income_statement", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_income_statement_parent": { + "name": "id_income_statement_parent", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_total": { + "name": "is_total", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_income_statement_id_organization_table_organization_id_fk": { + "name": "table_income_statement_id_organization_table_organization_id_fk", + "tableFrom": "table_income_statement", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_income_statement_id_year_table_year_id_fk": { + "name": "table_income_statement_id_year_table_year_id_fk", + "tableFrom": "table_income_statement", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_income_statement_id_income_statement_parent_table_income_statement_id_fk": { + "name": "table_income_statement_id_income_statement_parent_table_income_statement_id_fk", + "tableFrom": "table_income_statement", + "tableTo": "table_income_statement", + "columnsFrom": [ + "id_income_statement_parent" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_income_statement_created_by_table_user_id_fk": { + "name": "table_income_statement_created_by_table_user_id_fk", + "tableFrom": "table_income_statement", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_income_statement_last_updated_by_table_user_id_fk": { + "name": "table_income_statement_last_updated_by_table_user_id_fk", + "tableFrom": "table_income_statement", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_income_statement_id_organization_id_year_number_unique": { + "name": "table_income_statement_id_organization_id_year_number_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_inventory_item": { + "name": "table_inventory_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "sku": { + "name": "sku", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "unit_price": { + "name": "unit_price", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "current_quantity": { + "name": "current_quantity", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "minimum_threshold": { + "name": "minimum_threshold", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_inventory_item_id_organization_table_organization_id_fk": { + "name": "table_inventory_item_id_organization_table_organization_id_fk", + "tableFrom": "table_inventory_item", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_inventory_item_id_year_table_year_id_fk": { + "name": "table_inventory_item_id_year_table_year_id_fk", + "tableFrom": "table_inventory_item", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_inventory_item_created_by_table_user_id_fk": { + "name": "table_inventory_item_created_by_table_user_id_fk", + "tableFrom": "table_inventory_item", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_inventory_item_last_updated_by_table_user_id_fk": { + "name": "table_inventory_item_last_updated_by_table_user_id_fk", + "tableFrom": "table_inventory_item", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_inventory_item_id_organization_id_year_sku_unique": { + "name": "table_inventory_item_id_organization_id_year_sku_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "sku" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_inventory_movement": { + "name": "table_inventory_movement", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_inventory_item": { + "name": "id_inventory_item", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "quantity_change": { + "name": "quantity_change", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "unit_price_at_movement": { + "name": "unit_price_at_movement", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "reference": { + "name": "reference", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "movement_date": { + "name": "movement_date", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_inventory_movement_id_organization_table_organization_id_fk": { + "name": "table_inventory_movement_id_organization_table_organization_id_fk", + "tableFrom": "table_inventory_movement", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_inventory_movement_id_year_table_year_id_fk": { + "name": "table_inventory_movement_id_year_table_year_id_fk", + "tableFrom": "table_inventory_movement", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_inventory_movement_id_inventory_item_table_inventory_item_id_fk": { + "name": "table_inventory_movement_id_inventory_item_table_inventory_item_id_fk", + "tableFrom": "table_inventory_movement", + "tableTo": "table_inventory_item", + "columnsFrom": [ + "id_inventory_item" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_inventory_movement_created_by_table_user_id_fk": { + "name": "table_inventory_movement_created_by_table_user_id_fk", + "tableFrom": "table_inventory_movement", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_inventory_movement_last_updated_by_table_user_id_fk": { + "name": "table_inventory_movement_last_updated_by_table_user_id_fk", + "tableFrom": "table_inventory_movement", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_journal": { + "name": "table_journal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_journal_id_organization_table_organization_id_fk": { + "name": "table_journal_id_organization_table_organization_id_fk", + "tableFrom": "table_journal", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_journal_id_year_table_year_id_fk": { + "name": "table_journal_id_year_table_year_id_fk", + "tableFrom": "table_journal", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_journal_created_by_table_user_id_fk": { + "name": "table_journal_created_by_table_user_id_fk", + "tableFrom": "table_journal", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_journal_last_updated_by_table_user_id_fk": { + "name": "table_journal_last_updated_by_table_user_id_fk", + "tableFrom": "table_journal", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_journal_id_organization_id_year_code_unique": { + "name": "table_journal_id_organization_id_year_code_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_organization": { + "name": "table_organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "storage_limit": { + "name": "storage_limit", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1073741824 + }, + "storage_current_usage": { + "name": "storage_current_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_endpoint": { + "name": "storage_endpoint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_access_key": { + "name": "storage_access_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_secret_key": { + "name": "storage_secret_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_bucket_name": { + "name": "storage_bucket_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_region": { + "name": "storage_region", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_organization_created_by_table_user_id_fk": { + "name": "table_organization_created_by_table_user_id_fk", + "tableFrom": "table_organization", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_organization_last_updated_by_table_user_id_fk": { + "name": "table_organization_last_updated_by_table_user_id_fk", + "tableFrom": "table_organization", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_organization_user": { + "name": "table_organization_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_user": { + "name": "id_user", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "is_owner": { + "name": "is_owner", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_organization_user_id_user_index": { + "name": "table_organization_user_id_user_index", + "columns": [ + { + "expression": "id_user", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_organization_user_id_organization_table_organization_id_fk": { + "name": "table_organization_user_id_organization_table_organization_id_fk", + "tableFrom": "table_organization_user", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_organization_user_id_user_table_user_id_fk": { + "name": "table_organization_user_id_user_table_user_id_fk", + "tableFrom": "table_organization_user", + "tableTo": "table_user", + "columnsFrom": [ + "id_user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_organization_user_created_by_table_user_id_fk": { + "name": "table_organization_user_created_by_table_user_id_fk", + "tableFrom": "table_organization_user", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_organization_user_last_updated_by_table_user_id_fk": { + "name": "table_organization_user_last_updated_by_table_user_id_fk", + "tableFrom": "table_organization_user", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_organization_user_id_organization_id_user_unique": { + "name": "table_organization_user_id_organization_id_user_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_user" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_tag": { + "name": "table_tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year": { + "name": "id_year", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_tag_id_organization_table_organization_id_fk": { + "name": "table_tag_id_organization_table_organization_id_fk", + "tableFrom": "table_tag", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_tag_id_year_table_year_id_fk": { + "name": "table_tag_id_year_table_year_id_fk", + "tableFrom": "table_tag", + "tableTo": "table_year", + "columnsFrom": [ + "id_year" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_tag_created_by_table_user_id_fk": { + "name": "table_tag_created_by_table_user_id_fk", + "tableFrom": "table_tag", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_tag_last_updated_by_table_user_id_fk": { + "name": "table_tag_last_updated_by_table_user_id_fk", + "tableFrom": "table_tag", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_tag_id_organization_id_year_label_unique": { + "name": "table_tag_id_organization_id_year_label_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year", + "label" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_user": { + "name": "table_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "alias": { + "name": "alias", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_salt": { + "name": "password_salt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_user_email_unique": { + "name": "table_user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_user_session": { + "name": "table_user_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_user": { + "name": "id_user", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_user_session_id_user_index": { + "name": "table_user_session_id_user_index", + "columns": [ + { + "expression": "id_user", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_user_session_id_user_table_user_id_fk": { + "name": "table_user_session_id_user_table_user_id_fk", + "tableFrom": "table_user_session", + "tableTo": "table_user", + "columnsFrom": [ + "id_user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_year": { + "name": "table_year", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "id_organization": { + "name": "id_organization", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "id_year_previous": { + "name": "id_year_previous", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "is_closed": { + "name": "is_closed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "starting_at": { + "name": "starting_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "ending_at": { + "name": "ending_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": true + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp(0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_updated_by": { + "name": "last_updated_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "table_year_id_organization_table_organization_id_fk": { + "name": "table_year_id_organization_table_organization_id_fk", + "tableFrom": "table_year", + "tableTo": "table_organization", + "columnsFrom": [ + "id_organization" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "table_year_id_year_previous_table_year_id_fk": { + "name": "table_year_id_year_previous_table_year_id_fk", + "tableFrom": "table_year", + "tableTo": "table_year", + "columnsFrom": [ + "id_year_previous" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_year_created_by_table_user_id_fk": { + "name": "table_year_created_by_table_user_id_fk", + "tableFrom": "table_year", + "tableTo": "table_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "table_year_last_updated_by_table_user_id_fk": { + "name": "table_year_last_updated_by_table_user_id_fk", + "tableFrom": "table_year", + "tableTo": "table_user", + "columnsFrom": [ + "last_updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_year_id_organization_id_year_previous_unique": { + "name": "table_year_id_organization_id_year_previous_unique", + "nullsNotDistinct": false, + "columns": [ + "id_organization", + "id_year_previous" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "id": "1772b995-fcea-4a0a-ae35-a94e0704ad30", + "prevId": "00000000-0000-0000-0000-000000000000" +} \ No newline at end of file diff --git a/packages/tools/package.json b/packages/tools/package.json index 3a5f945e..11cb73cd 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -4,10 +4,10 @@ "private": true, "scripts": { "build:metadata": "pnpm --filter @comptasse/application-metadata build", - "generate": "pnpm drizzle-kit generate --config=./src/drizzle.config.ts", + "generate": "pnpm run build:metadata && tsx ./src/generateMigrations.ts", + "save-snapshot": "pnpm run build:metadata && tsx ./src/saveSnapshot.ts", "push": "pnpm run build:metadata && tsx ./src/push.ts", "pull": "pnpm drizzle-kit introspect --config=./src/drizzle.config.ts", - "migrate": "pnpm drizzle-kit migrate --config=./src/drizzle.config.ts", "seed": "pnpm run build:metadata && tsx ./src/seed/seed.ts", "migration": "pnpm run build:metadata && tsx ./src/seed/migration.ts", "clear": "pnpm run build:metadata && tsx ./src/clearDB.ts", diff --git a/packages/tools/src/generateMigrations.ts b/packages/tools/src/generateMigrations.ts new file mode 100644 index 00000000..a88a8946 --- /dev/null +++ b/packages/tools/src/generateMigrations.ts @@ -0,0 +1,40 @@ +import { modelSchemas } from "@comptasse/application-metadata" +import { generateDrizzleJson, generateMigration } from "drizzle-kit/api" +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs" +import { fileURLToPath } from "node:url" + +const repoDrizzleDir = fileURLToPath(new URL("../drizzle", import.meta.url)) +const defaultBaseline = fileURLToPath(new URL("../drizzle/meta/_snapshot.json", import.meta.url)) + +const outDir = process.env.MIGRATIONS_OUT || repoDrizzleDir +const baselinePath = process.env.MIGRATIONS_BASELINE || defaultBaseline +const setupFile = "0000_setup.sql" +const deltaFile = "0001_from_last_update.sql" + +function toSqlFile(statements: string[]) { + const body = statements + .map((statement) => statement.trim()) + .filter(Boolean) + .map((statement) => (statement.endsWith(";") ? statement : `${statement};`)) + .join("\n\n") + return body ? `${body}\n` : "" +} + +const cur = generateDrizzleJson(modelSchemas) + +const setup = await generateMigration(generateDrizzleJson({}), cur) + +let delta: string[] = [] +if (existsSync(baselinePath)) { + const prev = JSON.parse(readFileSync(baselinePath, "utf8")) + delta = await generateMigration(prev, cur) +} else { + console.warn(`[generateMigrations] No baseline snapshot found at ${baselinePath} - writing empty delta`) +} + +mkdirSync(outDir, { recursive: true }) +writeFileSync(`${outDir}/${setupFile}`, toSqlFile(setup)) +writeFileSync(`${outDir}/${deltaFile}`, toSqlFile(delta)) + +console.log(`[generateMigrations] setup: ${setup.length} statements, delta: ${delta.length} statements`) +console.log(`[generateMigrations] wrote ${setupFile} and ${deltaFile} to ${outDir}`) \ No newline at end of file diff --git a/packages/tools/src/saveSnapshot.ts b/packages/tools/src/saveSnapshot.ts new file mode 100644 index 00000000..4018db44 --- /dev/null +++ b/packages/tools/src/saveSnapshot.ts @@ -0,0 +1,14 @@ +import { modelSchemas } from "@comptasse/application-metadata" +import { generateDrizzleJson } from "drizzle-kit/api" +import { mkdirSync, writeFileSync } from "node:fs" +import { fileURLToPath } from "node:url" + +const baselinePath = + process.env.MIGRATIONS_BASELINE || + fileURLToPath(new URL("../drizzle/meta/_snapshot.json", import.meta.url)) + +const snapshot = generateDrizzleJson(modelSchemas) + +mkdirSync(baselinePath.replace(/\/[^/]+$/, ""), { recursive: true }) +writeFileSync(baselinePath, JSON.stringify(snapshot, null, 2)) +console.log(`[saveSnapshot] wrote baseline snapshot to ${baselinePath}`) \ No newline at end of file diff --git a/packages/website/public/install.sh b/packages/website/public/install.sh index b5c2dbd1..023d0a04 100644 --- a/packages/website/public/install.sh +++ b/packages/website/public/install.sh @@ -1,9 +1,9 @@ #!/bin/sh # -# Comptasse all-in-one installer (dashboard + API + CLI) +# Comptasse self-hosted installer (API + Dashboard + CLI) # # Usage: -# curl -fsSL https://comptasse.com/install.sh | sh # production (GHCR image) +# curl -fsSL https://comptasse.com/install.sh | sh # production (GHCR images) # curl -fsSL http://localhost:5173/install.sh | sh # local dev (built from source) # # Behaviour: @@ -12,24 +12,28 @@ # 3. Integrated: configures PostgreSQL and RustFS automatically. # External: uses the provided connection credentials. # 4. Generates a COOKIES_KEY session-signing key if none is provided. -# 5. Creates the data/config directory in ~/.comptasse. -# 6. Builds or pulls the Comptasse all-in-one image and starts the stack. +# 5. Creates the config directory in ~/.comptasse. +# 6. Pulls (production) or builds (local) the Comptasse API + Dashboard images +# and starts the stack. The website is hosted by the maintainers and is not +# part of self-hosted installs. +# 7. Installs the comptasse CLI on the host from GitHub Releases. # # Configuration (environment variables): # Image source (automatic, based on the origin the installer is served from): -# comptasse.com -> pull the published image from GHCR (production) -# any other -> build the image from a local repository checkout (local development) +# comptasse.com -> pull the published images from GHCR (production) +# any other -> build the images from a local repository checkout (local development) # COMPTASSE_SOURCE_ORIGIN default: https://comptasse.com # Origin the installer was fetched from; injected automatically by the # local dev server, and overridable for testing. # COMPTASSE_SERVICES=integrated|external default: integrated # COMPTASSE_DATA_DIR default: ~/.comptasse -# COMPTASSE_IMAGE full image reference (overrides the source logic) +# COMPTASSE_IMAGE base image name override (default: ghcr.io/comptasse/application) # COMPTASSE_REPO_DIR local development: path to the repository checkout # (optional; otherwise auto-detected from the working directory) # COMPTASSE_VERSION image tag (default: latest for registry, dev for local build) # COMPTASSE_API_PORT / COMPTASSE_DASHBOARD_PORT default: 3000 / 5173 # COMPTASSE_COOKIES_KEY optional; generated if absent +# COMPTASSE_SKIP_CLI=true skip installing the CLI on the host # External services only: # COMPTASSE_SQL_DATABASE_URL, COMPTASSE_STORAGE_ENDPOINT, # COMPTASSE_STORAGE_BUCKET_NAME, COMPTASSE_STORAGE_ACCESS_KEY, @@ -53,11 +57,12 @@ API_PORT="${COMPTASSE_API_PORT:-3000}" DASHBOARD_PORT="${COMPTASSE_DASHBOARD_PORT:-5173}" SERVICES="${COMPTASSE_SERVICES:-integrated}" SOURCE_ORIGIN="${COMPTASSE_SOURCE_ORIGIN:-https://comptasse.com}" +IMAGE_BASE="${COMPTASSE_IMAGE:-ghcr.io/comptasse/application}" INTERACTIVE=false [ -t 0 ] && INTERACTIVE=true -echo "Installing Comptasse (dashboard + API + CLI)" +echo "Installing Comptasse (API + Dashboard + CLI)" echo "" # ------------------------------------------------------------------------------ @@ -87,39 +92,31 @@ case "$SOURCE_ORIGIN" in *) IMAGE_SOURCE="local" ;; esac -IMAGE="${COMPTASSE_IMAGE:-}" -if [ -z "$IMAGE" ]; then - if [ "$IMAGE_SOURCE" = "local" ]; then - echo "[1/4] Preparing the local Comptasse image (no registry)..." - REPO_ROOT=$(_find_repo_root) || true - if [ -n "$REPO_ROOT" ]; then - ( - cd "$REPO_ROOT" && - # Supply placeholder values: .workflows/build/compose.yml interpolates - # these eagerly (they're runtime config), but the image BUILD does not - # need them. Real values come from the generated runtime compose.yml. - SQL_DATABASE_URL=postgres://placeholder:placeholder@postgres:5432/comptasse \ - STORAGE_ENDPOINT=http://rustfs:9000 \ - STORAGE_BUCKET_NAME=placeholder \ - STORAGE_ACCESS_KEY=placeholder \ - STORAGE_SECRET_KEY=placeholder \ - COMPTASSE_VERSION="${COMPTASSE_VERSION:-dev}" \ - docker compose -f .workflows/build/compose.yml build comptasse - ) - IMAGE="comptasse/comptasse:${COMPTASSE_VERSION:-dev}" - elif docker image inspect comptasse/comptasse:dev >/dev/null 2>&1; then - IMAGE="comptasse/comptasse:dev" - echo "Using the already-built local image $IMAGE." - else - echo "Error: local build requires the Comptasse repository checkout." >&2 - echo "Run the installer from the repository (or any of its subdirectories), or pass the checkout path:" >&2 - echo " curl -fsSL $SOURCE_ORIGIN/install.sh | COMPTASSE_REPO_DIR=/path/to/comptasse sh" >&2 - exit 1 - fi +if [ "$IMAGE_SOURCE" = "local" ]; then + echo "[1/5] Building the local Comptasse API + Dashboard images (no registry)..." + REPO_ROOT=$(_find_repo_root) || true + if [ -n "$REPO_ROOT" ]; then + ( + cd "$REPO_ROOT" && + COMPTASSE_VERSION="${COMPTASSE_VERSION:-dev}" \ + docker compose -f .workflows/build/compose.yml build api dashboard + ) + API_IMAGE="comptasse-api:${COMPTASSE_VERSION:-dev}" + DASHBOARD_IMAGE="comptasse-dashboard:${COMPTASSE_VERSION:-dev}" + elif docker image inspect "comptasse-api:${COMPTASSE_VERSION:-dev}" >/dev/null 2>&1; then + API_IMAGE="comptasse-api:${COMPTASSE_VERSION:-dev}" + DASHBOARD_IMAGE="comptasse-dashboard:${COMPTASSE_VERSION:-dev}" + echo "Using the already-built local images." else - echo "[1/4] Preparing to pull ghcr.io/comptasse/application/comptasse..." - IMAGE="ghcr.io/comptasse/application/comptasse:${COMPTASSE_VERSION:-latest}" + echo "Error: local build requires the Comptasse repository checkout." >&2 + echo "Run the installer from the repository (or any of its subdirectories), or pass the checkout path:" >&2 + echo " curl -fsSL $SOURCE_ORIGIN/install.sh | COMPTASSE_REPO_DIR=/path/to/comptasse sh" >&2 + exit 1 fi +else + echo "[1/5] Preparing to pull ${IMAGE_BASE}/{api,dashboard}..." + API_IMAGE="${IMAGE_BASE}/api:${COMPTASSE_VERSION:-latest}" + DASHBOARD_IMAGE="${IMAGE_BASE}/dashboard:${COMPTASSE_VERSION:-latest}" fi # ------------------------------------------------------------------------------ @@ -151,53 +148,64 @@ mkdir -p "$DATA_DIR" # Generate compose.yml + .env # ------------------------------------------------------------------------------ if [ "$SERVICES" = "integrated" ]; then - echo "[2/4] Configuring integrated services (PostgreSQL + RustFS)..." + echo "[2/5] Configuring integrated services (PostgreSQL + RustFS)..." cat > "$ENV_FILE" < "$COMPOSE_FILE" <<'EOF' services: - comptasse: - image: ${COMPTASSE_IMAGE} - container_name: comptasse + api: + image: ${API_IMAGE} + container_name: comptasse-api ports: - "${COMPTASSE_API_PORT}:3000" - - "${COMPTASSE_DASHBOARD_PORT}:5173" - volumes: - - ${COMPTASSE_DATA_MOUNT}:/data environment: + ENV: production + VERBOSE: "false" + PORT: "3000" + CORS_ORIGIN: ${CORS_ORIGIN} + COOKIES_DOMAIN: localhost + COOKIES_KEY: ${COOKIES_KEY:?COOKIES_KEY is required} + API_BASE_URL: http://localhost:${COMPTASSE_API_PORT} + WEBSITE_BASE_URL: https://comptasse.com + DASHBOARD_BASE_URL: http://localhost:${COMPTASSE_DASHBOARD_PORT} SQL_DATABASE_URL: postgres://postgres:password@postgres:5432/comptasse STORAGE_ENDPOINT: http://rustfs:9000 STORAGE_BUCKET_NAME: comptasse-files STORAGE_ACCESS_KEY: admin STORAGE_SECRET_KEY: admin STORAGE_REGION: fr-par - COOKIES_DOMAIN: localhost - CORS_ORIGIN: "*" - API_BASE_URL: http://localhost:3000 - WEBSITE_BASE_URL: http://localhost:5173 - DASHBOARD_BASE_URL: http://localhost:5173 - COOKIES_KEY: ${COOKIES_KEY:?COOKIES_KEY is required} depends_on: postgres: condition: service_healthy rustfs: condition: service_started healthcheck: - test: ["CMD-SHELL", "curl -f http://127.0.0.1:3000/ || curl -f http://localhost:5173/"] + test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:' + process.env.PORT, r => process.exit(r.statusCode < 500 ? 0 : 1)).on('error', () => process.exit(1))\""] interval: 10s timeout: 5s retries: 5 start_period: 60s restart: unless-stopped + dashboard: + image: ${DASHBOARD_IMAGE} + container_name: comptasse-dashboard + ports: + - "${COMPTASSE_DASHBOARD_PORT}:80" + depends_on: + - api + restart: unless-stopped + postgres: image: postgres:18.1 + container_name: comptasse-postgres volumes: - postgres-data:/var/lib/postgresql environment: @@ -213,6 +221,7 @@ services: rustfs: image: rustfs/rustfs:latest + container_name: comptasse-rustfs volumes: - rustfs-data:/data environment: @@ -227,7 +236,7 @@ volumes: rustfs-data: EOF else - echo "[2/4] Configuring external services (your PostgreSQL + S3)..." + echo "[2/5] Configuring external services (your PostgreSQL + S3)..." SQL_DATABASE_URL="${COMPTASSE_SQL_DATABASE_URL:-}" STORAGE_ENDPOINT="${COMPTASSE_STORAGE_ENDPOINT:-}" @@ -260,10 +269,11 @@ else STORAGE_REGION="${COMPTASSE_STORAGE_REGION:-fr-par}" cat > "$ENV_FILE" < "$COMPOSE_FILE" <<'EOF' services: - comptasse: - image: ${COMPTASSE_IMAGE} - container_name: comptasse + api: + image: ${API_IMAGE} + container_name: comptasse-api ports: - "${COMPTASSE_API_PORT}:3000" - - "${COMPTASSE_DASHBOARD_PORT}:5173" - volumes: - - ${COMPTASSE_DATA_MOUNT}:/data environment: + ENV: production + VERBOSE: "false" + PORT: "3000" + CORS_ORIGIN: ${CORS_ORIGIN} + COOKIES_DOMAIN: localhost + COOKIES_KEY: ${COOKIES_KEY:?required} + API_BASE_URL: http://localhost:${COMPTASSE_API_PORT} + WEBSITE_BASE_URL: https://comptasse.com + DASHBOARD_BASE_URL: http://localhost:${COMPTASSE_DASHBOARD_PORT} SQL_DATABASE_URL: ${SQL_DATABASE_URL:?required} STORAGE_ENDPOINT: ${STORAGE_ENDPOINT:?required} STORAGE_BUCKET_NAME: ${STORAGE_BUCKET_NAME:?required} STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY:?required} STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY:?required} STORAGE_REGION: ${STORAGE_REGION:-fr-par} - COOKIES_KEY: ${COOKIES_KEY:?required} + healthcheck: + test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:' + process.env.PORT, r => process.exit(r.statusCode < 500 ? 0 : 1)).on('error', () => process.exit(1))\""] + interval: 10s + timeout: 5s + retries: 5 + start_period: 60s restart: unless-stopped -volumes: - comptasse-data: + dashboard: + image: ${DASHBOARD_IMAGE} + container_name: comptasse-dashboard + ports: + - "${COMPTASSE_DASHBOARD_PORT}:80" + depends_on: + - api + restart: unless-stopped EOF fi +# ------------------------------------------------------------------------------ +# Install the CLI on the host (from GitHub Releases) +# ------------------------------------------------------------------------------ +if [ "${COMPTASSE_SKIP_CLI:-false}" = "true" ]; then + echo "[3/5] Skipping CLI installation (COMPTASSE_SKIP_CLI=true)." +else + echo "[3/5] Installing the comptasse CLI on the host..." + CLI_INSTALL_DIR="${COMPTASSE_INSTALL_DIR:-$HOME/.local/bin}" + CLI_DEST="${CLI_INSTALL_DIR}/comptasse" + mkdir -p "$CLI_INSTALL_DIR" + curl -fsSL --progress-bar "https://github.com/comptasse/application/releases/latest/download/comptasse.sh" -o "$CLI_DEST" + chmod +x "$CLI_DEST" + echo "Installed CLI: $CLI_DEST" +fi + # ------------------------------------------------------------------------------ # Start # ------------------------------------------------------------------------------ -echo "[3/4] Starting Comptasse (image: $IMAGE)..." +echo "[4/5] Starting Comptasse (api: $API_IMAGE, dashboard: $DASHBOARD_IMAGE)..." docker compose --project-name comptasse --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d -echo "[4/4] " +echo "[5/5] " echo "" echo "Installation complete" echo "" echo " Dashboard: http://localhost:$DASHBOARD_PORT" echo " API: http://localhost:$API_PORT" -echo " CLI: docker exec comptasse comptasse --help" +if [ "${COMPTASSE_SKIP_CLI:-false}" != "true" ]; then + echo " CLI: $CLI_DEST --help" +fi echo "" echo " Config: $DATA_DIR" echo " Services: docker compose --project-name comptasse -f $COMPOSE_FILE ps" -echo " Logs: docker compose --project-name comptasse -f $COMPOSE_FILE logs -f comptasse" +echo " Logs: docker compose --project-name comptasse -f $COMPOSE_FILE logs -f api" echo "" echo "Next steps: open the Dashboard, create your account and your first organization." \ No newline at end of file diff --git a/packages/website/src/features/docs/guide/InstallationGuideDocPage.tsx b/packages/website/src/features/docs/guide/InstallationGuideDocPage.tsx index f6687ae3..531ae0df 100644 --- a/packages/website/src/features/docs/guide/InstallationGuideDocPage.tsx +++ b/packages/website/src/features/docs/guide/InstallationGuideDocPage.tsx @@ -39,7 +39,7 @@ export function InstallationGuideDocPage() { Le script d'installation guide pas à pas et configure automatiquement l'environnement. Il fonctionne - sur macOS et Linux. + sur macOS et Linux et installe l'API, le Dashboard et le CLI. @@ -54,17 +54,18 @@ export function InstallationGuideDocPage() { "Si services intégrés : configure PostgreSQL et RustFS automatiquement", "Si services externes : demande les identifiants de connexion", "Génère une clé de signature des sessions (COOKIES_KEY) si non fournie", - "Crée le répertoire de données dans ~/.comptasse", - "Télécharge et démarre l'image Docker Comptasse", + "Crée le répertoire de configuration dans ~/.comptasse", + "Télécharge et démarre les images Docker API + Dashboard (le site web est hébergé par l'équipe Comptasse)", + "Installe le CLI sur la machine hôte depuis les GitHub Releases", ]} /> - Le script choisit la source de l'image en fonction de l'origine du téléchargement : depuis - https://comptasse.com (production), il télécharge l'image publiée (dashboard + - API + CLI) sur GHCR ; depuis tout autre origine, par exemple{" "} - http://localhost en développement, il construit l'image à partir des sources + Le script choisit la source des images en fonction de l'origine du téléchargement : depuis + https://comptasse.com (production), il télécharge les images publiées (API + + Dashboard) sur GHCR ; depuis tout autre origine, par exemple{" "} + http://localhost en développement, il construit les images à partir des sources sans passer par un registre — ce qui requiert une copie du dépôt Comptasse sur la machine. @@ -73,7 +74,7 @@ export function InstallationGuideDocPage() { items={[ "Dashboard : http://localhost:5173", "API : http://localhost:3000", - "CLI : docker exec comptasse comptasse --help", + "CLI : comptasse --help (installé dans ~/.local/bin)", ]} /> @@ -81,24 +82,37 @@ export function InstallationGuideDocPage() { - Pour un contrôle total sur la configuration, utilisez directement docker run. + Pour un contrôle total sur la configuration, lancez les conteneurs API et Dashboard avec{" "} + docker run. {`docker run -d \\ - --name comptasse \\ + --name comptasse-api \\ -p 3000:3000 \\ - -p 5173:5173 \\ - -v comptasse-data:/data \\ + -e ENV=production \\ + -e VERBOSE=false \\ + -e PORT=3000 \\ + -e CORS_ORIGIN=http://localhost:5173 \\ + -e COOKIES_DOMAIN=localhost \\ + -e COOKIES_KEY=UNE_CLE_SIGNATURE_32_CARACTERES \\ + -e API_BASE_URL=http://localhost:3000 \\ + -e WEBSITE_BASE_URL=https://comptasse.com \\ + -e DASHBOARD_BASE_URL=http://localhost:5173 \\ -e SQL_DATABASE_URL=postgres://user:password@host:5432/comptasse \\ -e STORAGE_ENDPOINT=https://s3.amazonaws.com \\ -e STORAGE_BUCKET_NAME=my-bucket \\ -e STORAGE_ACCESS_KEY=VOTRE_CLE_ACCES_S3 \\ -e STORAGE_SECRET_KEY=VOTRE_CLE_SECRETE_S3 \\ - comptasse/comptasse`} + ghcr.io/comptasse/application/api + +docker run -d \\ + --name comptasse-dashboard \\ + -p 5173:80 \\ + ghcr.io/comptasse/application/dashboard`} - + @@ -116,19 +131,27 @@ export function InstallationGuideDocPage() { Si vous n'avez pas de PostgreSQL ou de S3, ce fichier compose.yml inclut tout : - Comptasse, PostgreSQL et RustFS (stockage S3). + l'API, le Dashboard, PostgreSQL et RustFS (stockage S3). Le Dashboard est construit avec{" "} + VITE_API_BASE_URL=/api et son nginx relaie les requêtes{" "} + /api vers le service api. {`services: - comptasse: - image: comptasse/comptasse + api: + image: ghcr.io/comptasse/application/api ports: - "3000:3000" - - "5173:5173" - volumes: - - comptasse-data:/data environment: + ENV: production + VERBOSE: "false" + PORT: "3000" + CORS_ORIGIN: http://localhost:5173 + COOKIES_DOMAIN: localhost + COOKIES_KEY: UNE_CLE_SIGNATURE_32_CARACTERES + API_BASE_URL: http://localhost:3000 + WEBSITE_BASE_URL: https://comptasse.com + DASHBOARD_BASE_URL: http://localhost:5173 SQL_DATABASE_URL: postgres://postgres:password@postgres:5432/comptasse STORAGE_ENDPOINT: http://rustfs:9000 STORAGE_BUCKET_NAME: comptasse-files @@ -141,10 +164,18 @@ export function InstallationGuideDocPage() { condition: service_started restart: unless-stopped + dashboard: + image: ghcr.io/comptasse/application/dashboard + ports: + - "5173:80" + depends_on: + - api + restart: unless-stopped + postgres: image: postgres:18.1 volumes: - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password @@ -168,7 +199,6 @@ export function InstallationGuideDocPage() { restart: unless-stopped volumes: - comptasse-data: postgres-data: rustfs-data:`} @@ -205,8 +235,8 @@ docker compose ps`} - Le CLI est un client HTTP autonome qui communique avec l'API. Il peut être installé sur votre - machine hôte ou utilisé directement dans le conteneur. + Le CLI est un client HTTP autonome qui communique avec l'API. Il est distribué via les GitHub + Releases et s'installe sur votre machine hôte. @@ -218,15 +248,7 @@ comptasse --version # Connectez-vous à votre instance comptasse login --api-key VOTRE_CLE_API --url http://localhost:3000`} - - - {`# Accédez au CLI dans le conteneur -docker exec -it comptasse comptasse --help - -# Ou exécutez des commandes directement -docker exec comptasse comptasse whoami`} - ) -} +} \ No newline at end of file diff --git a/packages/website/src/features/docs/guide/MigrationsGuideDocPage.tsx b/packages/website/src/features/docs/guide/MigrationsGuideDocPage.tsx index f8a348e0..257fdf15 100644 --- a/packages/website/src/features/docs/guide/MigrationsGuideDocPage.tsx +++ b/packages/website/src/features/docs/guide/MigrationsGuideDocPage.tsx @@ -8,8 +8,10 @@ import { DocParagraph } from "../../../components/document/DocParagraph.js" import { DocRoot } from "../../../components/document/DocRoot.js" import { DocSection } from "../../../components/document/DocSection.js" import { DocTip } from "../../../components/document/DocTip.js" +import { useSiteOrigin } from "../../../utilities/useSiteOrigin.js" export function MigrationsGuideDocPage() { + const origin = useSiteOrigin() return ( - + - Lorsque vous mettez à jour Comptasse, le schéma de la base de données - peut évoluer. Le conteneur vérifie automatiquement la synchronisation - au démarrage. + Les images API embarquent les fichiers de migration SQL générés au moment de la + construction : 0000_setup.sql, + crée le schéma complet (utilisé pour une nouvelle installation), + 0001_from_last_update.sql, + + contient les changements depuis la dernière version publiée (vide s'il n'y a pas de + changement) + , ]} /> + + Au démarrage du conteneur API, la migration est appliquée automatiquement avant le lancement + du serveur : + + + + Les fichiers SQL de la version courante sont consultables sur{" "} + + {`${origin}/migrations/`} + {" "} + pour les passer en revue avant une mise à jour. + - Si vous voyez l'erreur{" "} - Database schema is out of sync au démarrage, cela - signifie que le schéma de la base de données n'est pas compatible avec - la version de Comptasse que vous utilisez. + L'API vérifie le schéma au démarrage. Si vous voyez l'erreur{" "} + Database schema is out of sync, la base de données n'est pas compatible + avec la version des images. Redémarrez le conteneur API pour ré-appliquer les migrations : + {`# Redémarrez le conteneur API (les migrations s'exécutent au démarrage) +docker compose --project-name comptasse restart api - - - Cette commande applique les modifications de schéma sans perdre de - données. - - {`docker exec comptasse pnpm --filter @comptasse/application-tools run push`} - - - - - Cette commande supprime toutes les données de la base de - données. Utilisez-la uniquement en développement ou si vous - acceptez la perte de données. - - {`docker exec comptasse pnpm --filter @comptasse/application-tools run reset`} - +# Consultez les logs pour voir le résultat de la migration +docker compose --project-name comptasse logs api`} - - - {`# Vérifier la synchronisation du schéma -docker exec comptasse pnpm --filter @comptasse/application-api exec tsx --conditions source ./src/server.ts -# (avec SCHEMA_CHECK_ONLY=1 pour un check rapide)`} - - - - {`# Appliquer les modifications de schéma -docker exec comptasse pnpm --filter @comptasse/application-tools run push`} - - - - {`# Générer des fichiers de migration SQL -docker exec comptasse pnpm --filter @comptasse/application-tools run generate`} - - - - {`# Appliquer les migrations générées -docker exec comptasse pnpm --filter @comptasse/application-tools run migrate`} - - - - {`# Supprimer toutes les tables, pousser le schéma, et charger les données de test -docker exec comptasse pnpm --filter @comptasse/application-tools run reset`} + + + Pendant le développement, le schéma est géré avec les commandes du dépôt : + + + {`# Depuis la racine du dépôt, avec l'environnement de développement lancé +just db-push`} - - - - - Si une table attendue par le code n'existe pas dans la base de - données, exécutez : - - {`docker exec comptasse pnpm --filter @comptasse/application-tools run push`} + + {`# Régénère 0000_setup.sql et 0001_from_last_update.sql depuis les modèles +pnpm --filter @comptasse/application-tools run generate`} - - - Si une colonne attendue par le code n'existe pas, la commande push - l'ajoutera automatiquement. - - {`docker exec comptasse pnpm --filter @comptasse/application-tools run push`} + + {`# Après avoir validé un changement de schéma, verrouille la nouvelle référence +pnpm --filter @comptasse/application-tools run save-snapshot`} - - - Si la base de données contient des tables ou colonnes qui n'existent - plus dans le code, vous pouvez les supprimer manuellement ou - réinitialiser la base de données. - - {`# Option A : supprimer manuellement les tables/colonnes obsolètes -# Option B : réinitialiser la base de données (ATTENTION : perte de données) -docker exec comptasse pnpm --filter @comptasse/application-tools run reset`} + + {`# Supprime toutes les tables, pousse le schéma, et charge les données de test +just db-reset`} @@ -119,10 +97,9 @@ docker exec comptasse pnpm --filter @comptasse/application-tools run reset`} @@ -133,4 +110,4 @@ docker exec comptasse pnpm --filter @comptasse/application-tools run reset`} ) -} +} \ No newline at end of file diff --git a/packages/website/src/features/docs/guide/StartGuideDocPage.tsx b/packages/website/src/features/docs/guide/StartGuideDocPage.tsx index 4e1e064f..040f4f33 100644 --- a/packages/website/src/features/docs/guide/StartGuideDocPage.tsx +++ b/packages/website/src/features/docs/guide/StartGuideDocPage.tsx @@ -58,8 +58,8 @@ export function StartGuideDocPage() { Le CLI est un client HTTP autonome. Installez-le avec{" "} - {`curl -fsSL ${origin}/cli/install.sh | sh`} ou utilisez-le directement dans le - conteneur avec docker exec comptasse comptasse --help. + {`curl -fsSL ${origin}/cli/install.sh | sh`} puis lancez{" "} + comptasse --help. diff --git a/packages/website/src/utilities/useSiteOrigin.ts b/packages/website/src/utilities/useSiteOrigin.ts index 2beae067..33e66987 100644 --- a/packages/website/src/utilities/useSiteOrigin.ts +++ b/packages/website/src/utilities/useSiteOrigin.ts @@ -1,13 +1,11 @@ -import { useEffect, useState } from "react" +import { useState } from "react" const DEFAULT_ORIGIN = "https://comptasse.com" export function useSiteOrigin() { - const [origin, setOrigin] = useState(DEFAULT_ORIGIN) - - useEffect(() => { - setOrigin(window.location.origin) - }, []) + const [origin] = useState(() => + typeof window === "undefined" ? DEFAULT_ORIGIN : window.location.origin, + ) return origin }