feat: Grundgeruest fertigstellen (Backend/Frontend/K8s)
Backend: Prisma-Client auf prisma-client-js + Driver-Adapter (@prisma/adapter-pg) umgestellt (Prisma 7 verlangt Adapter, ESM-Client brach an Jest), erste Migration erzeugt und angewendet, /api/v1 Versioning, Swagger unter /api/v1/docs, Helmet, globale ValidationPipe, PrismaModule, HealthModule (/health, /health/ready mit DB-Check), tsconfig.build.json Root-Dir-Fix (dist/main.js statt dist/src/main.js). Frontend: Tailwind v4 + shadcn/ui initialisiert, TanStack Query Provider, PWA Manifest (app/manifest.ts), Vitest + Testing Library Setup, next.config output standalone. deploy/k8s: Dockerfiles (Backend/Frontend, Multi-Stage) + Deployment/Service/ Ingress/ConfigMap/Secret-Vorlagen fuer beide Projekte. Lokale Dev-/Test-Postgres-Instanz unter ApiCore-Backend/.devdb angelegt (eigene Rolle, kein bestehendes Passwort noetig), .env/.env.test lokal befuellt. Lint, Typecheck, Tests und Build sind fuer Backend und Frontend gruen; Server wurde lokal gestartet und /health, /health/ready, /api/v1/docs manuell verifiziert.
This commit is contained in:
1 parent
c4a2d0648e
commit
eae3fea957
57 files changed
+15114
-35
No files matched your search
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
dist
|
||||
generated
|
||||
coverage
|
||||
.devdb
|
||||
.env
|
||||
.env.test
|
||||
*.log
|
||||
@@ -1,5 +1,13 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
*.tsbuildinfo
|
||||
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
.env.test
|
||||
|
||||
/generated/prisma
|
||||
|
||||
# Local dev/test PostgreSQL data dir (not for production use)
|
||||
.devdb/
|
||||
@@ -0,0 +1,20 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-alpine AS runtime
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/generated ./generated
|
||||
EXPOSE 3001
|
||||
USER node
|
||||
CMD ["node", "dist/main.js"]
|
||||
Generated
+188
-1
@@ -16,6 +16,7 @@
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.4.7",
|
||||
"@prisma/adapter-pg": "^7.9.1",
|
||||
"@prisma/client": "^7.9.1",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -2703,6 +2704,18 @@
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/adapter-pg": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.9.1.tgz",
|
||||
"integrity": "sha512-Ho2RK1KanQxLNSC0sR5bpiiVep10sWPLXCcxK+KXfI/Q69TMRbiafSvLPv3V9snimX72rMCqGlyJ4sBO4lKTAw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/driver-adapter-utils": "7.9.1",
|
||||
"@types/pg": "^8.16.0",
|
||||
"pg": "^8.16.3",
|
||||
"postgres-array": "3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.9.1.tgz",
|
||||
@@ -2750,7 +2763,6 @@
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.9.1.tgz",
|
||||
"integrity": "sha512-/cpVZ4itxtcgB8GHBvZtcmuEjq+lWsLrRJxFMbwZrT1RIdtuKmUm7PPGo/wzfbYpBrk+9WmmBE8CHJw2rybKDQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/dev": {
|
||||
@@ -2777,6 +2789,15 @@
|
||||
"zeptomatch": "2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/driver-adapter-utils": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.9.1.tgz",
|
||||
"integrity": "sha512-vmHehG7nn/heW32DXXpp13DxxAxVVe6n250oEt3dOL2E/4bt3olktKZN0mzSuxMMronyMSkbeW2uCOn3F4g8RQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "7.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.9.1.tgz",
|
||||
@@ -3527,6 +3548,17 @@
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
"version": "8.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz",
|
||||
"integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
"pg-types": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
|
||||
@@ -9790,6 +9822,104 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.23.0",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz",
|
||||
"integrity": "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.14.0",
|
||||
"pg-pool": "^3.14.0",
|
||||
"pg-protocol": "^1.16.0",
|
||||
"pg-types": "2.2.0",
|
||||
"pgpass": "1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pg-cloudflare": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-native": ">=3.0.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pg-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pg-cloudflare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
|
||||
"integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz",
|
||||
"integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
|
||||
"integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz",
|
||||
"integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-types/node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -9925,6 +10055,45 @@
|
||||
"url": "https://github.com/sponsors/porsager"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
|
||||
"integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
|
||||
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@@ -10646,6 +10815,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
@@ -12028,6 +12206,15 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.4.7",
|
||||
"@prisma/adapter-pg": "^7.9.1",
|
||||
"@prisma/client": "^7.9.1",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
|
||||
@@ -0,0 +1,640 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('ADMIN', 'BEEKEEPER', 'STAFF', 'READONLY', 'API_CLIENT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AuditAction" AS ENUM ('CREATE', 'UPDATE', 'DEACTIVATE', 'DELETE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ApiaryStatus" AS ENUM ('ACTIVE', 'INACTIVE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ColonyType" AS ENUM ('PRODUCTION_COLONY', 'NUCLEUS_COLONY', 'YOUNG_COLONY', 'CARETAKER_COLONY', 'MATING_NUCLEUS', 'RESERVE_COLONY');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ColonyStatus" AS ENUM ('ACTIVE', 'WEAK', 'CONSPICUOUS', 'QUEENLESS', 'DISSOLVED', 'SOLD', 'DECEASED', 'MERGED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LineageType" AS ENUM ('SPLIT', 'MERGE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "QueenStatus" AS ENUM ('ACTIVE', 'RESERVE', 'UNMATED', 'MATED', 'LOST', 'REPLACED', 'SOLD', 'DECEASED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TaskPriority" AS ENUM ('LOW', 'NORMAL', 'HIGH', 'URGENT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TaskStatus" AS ENUM ('OPEN', 'IN_PROGRESS', 'DONE', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TaskRelatedType" AS ENUM ('COLONY', 'APIARY', 'QUEEN', 'HONEY_BATCH', 'STORAGE', 'GENERAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VarroaMethod" AS ENUM ('NATURAL_MITE_FALL', 'POWDERED_SUGAR', 'WASHING_METHOD', 'DRONE_BROOD_CONTROL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "FeedType" AS ENUM ('SUGAR_WATER', 'FEED_SYRUP', 'FEED_PASTE', 'OWN_HONEY', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "HoneyBatchStatus" AS ENUM ('RAW', 'EXTRACTED', 'CLARIFIED', 'CREAMED', 'STORED', 'BOTTLED', 'SOLD', 'CONSUMED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StorageContainerStatus" AS ENUM ('EMPTY', 'FILLED', 'CLEANING', 'OUT_OF_SERVICE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StockCategory" AS ENUM ('HONEY', 'CONSUMABLE', 'EQUIPMENT_MATERIAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StockMovementType" AS ENUM ('IN', 'OUT', 'TRANSFER', 'ADJUSTMENT');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"role" "Role" NOT NULL DEFAULT 'STAFF',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deactivatedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RefreshToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"tokenHash" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AuditLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"entityType" TEXT NOT NULL,
|
||||
"entityId" TEXT NOT NULL,
|
||||
"action" "AuditAction" NOT NULL,
|
||||
"userId" TEXT,
|
||||
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"beforeData" JSONB,
|
||||
"afterData" JSONB,
|
||||
|
||||
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Apiary" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"location" TEXT,
|
||||
"latitude" DOUBLE PRECISION,
|
||||
"longitude" DOUBLE PRECISION,
|
||||
"address" TEXT,
|
||||
"status" "ApiaryStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deactivatedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Apiary_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Colony" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"apiaryId" TEXT NOT NULL,
|
||||
"hiveType" TEXT,
|
||||
"originText" TEXT,
|
||||
"colonyType" "ColonyType" NOT NULL,
|
||||
"status" "ColonyStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"strength" INTEGER,
|
||||
"broodStatus" TEXT,
|
||||
"feedStatus" TEXT,
|
||||
"healthStatus" TEXT,
|
||||
"notes" TEXT,
|
||||
"currentQueenId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deactivatedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Colony_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ColonyApiaryHistory" (
|
||||
"id" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
"apiaryId" TEXT NOT NULL,
|
||||
"movedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"movedByUserId" TEXT,
|
||||
"notes" TEXT,
|
||||
|
||||
CONSTRAINT "ColonyApiaryHistory_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ColonyLineage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" "LineageType" NOT NULL,
|
||||
"fromColonyId" TEXT NOT NULL,
|
||||
"toColonyId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ColonyLineage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Queen" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"hatchYear" INTEGER,
|
||||
"breed" TEXT,
|
||||
"origin" TEXT,
|
||||
"breeder" TEXT,
|
||||
"markingColor" TEXT,
|
||||
"markingNumber" TEXT,
|
||||
"matingType" TEXT,
|
||||
"matingStatus" TEXT,
|
||||
"inseminationData" TEXT,
|
||||
"motherLine" TEXT,
|
||||
"fatherLine" TEXT,
|
||||
"status" "QueenStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deactivatedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Queen_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "QueenColonyHistory" (
|
||||
"id" TEXT NOT NULL,
|
||||
"queenId" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
"startDate" TIMESTAMP(3) NOT NULL,
|
||||
"endDate" TIMESTAMP(3),
|
||||
"notes" TEXT,
|
||||
|
||||
CONSTRAINT "QueenColonyHistory_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Inspection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"weather" TEXT,
|
||||
"temperature" DOUBLE PRECISION,
|
||||
"colonyStrength" INTEGER,
|
||||
"broodAmount" TEXT,
|
||||
"broodPattern" INTEGER,
|
||||
"openBroodPresent" BOOLEAN,
|
||||
"cappedBroodPresent" BOOLEAN,
|
||||
"eggsPresent" BOOLEAN,
|
||||
"queenSeen" BOOLEAN,
|
||||
"queenCellsPresent" BOOLEAN,
|
||||
"swarmMood" INTEGER,
|
||||
"gentleness" INTEGER,
|
||||
"combBehavior" INTEGER,
|
||||
"feedStatus" TEXT,
|
||||
"honeySuperStatus" TEXT,
|
||||
"healthIssues" TEXT,
|
||||
"varroaIssues" TEXT,
|
||||
"measures" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Inspection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Task" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"priority" "TaskPriority" NOT NULL DEFAULT 'NORMAL',
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"status" "TaskStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"assignedUserId" TEXT,
|
||||
"createdByUserId" TEXT,
|
||||
"relatedType" "TaskRelatedType" NOT NULL DEFAULT 'GENERAL',
|
||||
"relatedId" TEXT,
|
||||
"sourceInspectionId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"completedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Task_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VarroaMeasurement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"method" "VarroaMethod" NOT NULL,
|
||||
"durationMinutes" INTEGER,
|
||||
"miteCount" INTEGER,
|
||||
"calculatedValue" DOUBLE PRECISION,
|
||||
"assessment" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "VarroaMeasurement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Treatment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"startDate" TIMESTAMP(3),
|
||||
"endDate" TIMESTAMP(3),
|
||||
"product" TEXT NOT NULL,
|
||||
"activeIngredient" TEXT,
|
||||
"amount" TEXT,
|
||||
"method" TEXT,
|
||||
"reason" TEXT,
|
||||
"batchNumber" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Treatment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Feeding" (
|
||||
"id" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"feedType" "FeedType" NOT NULL,
|
||||
"product" TEXT,
|
||||
"amount" DOUBLE PRECISION NOT NULL,
|
||||
"unit" TEXT NOT NULL,
|
||||
"batchNumber" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Feeding_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Harvest" (
|
||||
"id" TEXT NOT NULL,
|
||||
"apiaryId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"honeyType" TEXT,
|
||||
"honeySuperCount" INTEGER,
|
||||
"grossWeight" DOUBLE PRECISION,
|
||||
"netWeight" DOUBLE PRECISION,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Harvest_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "HarvestColony" (
|
||||
"id" TEXT NOT NULL,
|
||||
"harvestId" TEXT NOT NULL,
|
||||
"colonyId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "HarvestColony_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Extraction" (
|
||||
"id" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"amount" DOUBLE PRECISION,
|
||||
"room" TEXT,
|
||||
"waterContent" DOUBLE PRECISION,
|
||||
"temperature" DOUBLE PRECISION,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Extraction_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ExtractionHarvest" (
|
||||
"id" TEXT NOT NULL,
|
||||
"extractionId" TEXT NOT NULL,
|
||||
"harvestId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ExtractionHarvest_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "HoneyBatch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"batchNumber" TEXT NOT NULL,
|
||||
"extractionId" TEXT NOT NULL,
|
||||
"honeyType" TEXT,
|
||||
"amount" DOUBLE PRECISION,
|
||||
"waterContent" DOUBLE PRECISION,
|
||||
"storageContainerId" TEXT,
|
||||
"status" "HoneyBatchStatus" NOT NULL DEFAULT 'RAW',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "HoneyBatch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "HoneyMeasurement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"honeyBatchId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"waterContent" DOUBLE PRECISION NOT NULL,
|
||||
"measuredAt" TIMESTAMP(3) NOT NULL,
|
||||
"device" TEXT,
|
||||
"temperature" DOUBLE PRECISION,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "HoneyMeasurement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StorageContainer" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"containerType" TEXT,
|
||||
"capacity" DOUBLE PRECISION,
|
||||
"currentAmount" DOUBLE PRECISION,
|
||||
"location" TEXT,
|
||||
"status" "StorageContainerStatus" NOT NULL DEFAULT 'EMPTY',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "StorageContainer_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StockItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"category" "StockCategory" NOT NULL,
|
||||
"unit" TEXT NOT NULL,
|
||||
"quantity" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"minThreshold" DOUBLE PRECISION,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "StockItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StockMovement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"stockItemId" TEXT NOT NULL,
|
||||
"type" "StockMovementType" NOT NULL,
|
||||
"quantity" DOUBLE PRECISION NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" TEXT,
|
||||
"relatedType" TEXT,
|
||||
"relatedId" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "StockMovement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_role_idx" ON "User"("role");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RefreshToken_tokenHash_key" ON "RefreshToken"("tokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_entityType_entityId_idx" ON "AuditLog"("entityType", "entityId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_timestamp_idx" ON "AuditLog"("timestamp");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Apiary_status_idx" ON "Apiary"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Colony_code_key" ON "Colony"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Colony_currentQueenId_key" ON "Colony"("currentQueenId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Colony_apiaryId_idx" ON "Colony"("apiaryId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Colony_status_idx" ON "Colony"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ColonyApiaryHistory_colonyId_idx" ON "ColonyApiaryHistory"("colonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ColonyApiaryHistory_apiaryId_idx" ON "ColonyApiaryHistory"("apiaryId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ColonyLineage_fromColonyId_idx" ON "ColonyLineage"("fromColonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ColonyLineage_toColonyId_idx" ON "ColonyLineage"("toColonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Queen_code_key" ON "Queen"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Queen_status_idx" ON "Queen"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QueenColonyHistory_queenId_idx" ON "QueenColonyHistory"("queenId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "QueenColonyHistory_colonyId_idx" ON "QueenColonyHistory"("colonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Inspection_colonyId_date_idx" ON "Inspection"("colonyId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Task_status_dueDate_idx" ON "Task"("status", "dueDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Task_relatedType_relatedId_idx" ON "Task"("relatedType", "relatedId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VarroaMeasurement_colonyId_date_idx" ON "VarroaMeasurement"("colonyId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Treatment_colonyId_date_idx" ON "Treatment"("colonyId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Feeding_colonyId_date_idx" ON "Feeding"("colonyId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Harvest_apiaryId_date_idx" ON "Harvest"("apiaryId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "HarvestColony_colonyId_idx" ON "HarvestColony"("colonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "HarvestColony_harvestId_colonyId_key" ON "HarvestColony"("harvestId", "colonyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Extraction_date_idx" ON "Extraction"("date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ExtractionHarvest_harvestId_idx" ON "ExtractionHarvest"("harvestId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ExtractionHarvest_extractionId_harvestId_key" ON "ExtractionHarvest"("extractionId", "harvestId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "HoneyBatch_batchNumber_key" ON "HoneyBatch"("batchNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "HoneyBatch_status_idx" ON "HoneyBatch"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "HoneyBatch_extractionId_idx" ON "HoneyBatch"("extractionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "HoneyMeasurement_honeyBatchId_idx" ON "HoneyMeasurement"("honeyBatchId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "StorageContainer_code_key" ON "StorageContainer"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StorageContainer_status_idx" ON "StorageContainer"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockItem_category_idx" ON "StockItem"("category");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockMovement_stockItemId_date_idx" ON "StockMovement"("stockItemId", "date");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Colony" ADD CONSTRAINT "Colony_apiaryId_fkey" FOREIGN KEY ("apiaryId") REFERENCES "Apiary"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Colony" ADD CONSTRAINT "Colony_currentQueenId_fkey" FOREIGN KEY ("currentQueenId") REFERENCES "Queen"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ColonyApiaryHistory" ADD CONSTRAINT "ColonyApiaryHistory_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ColonyApiaryHistory" ADD CONSTRAINT "ColonyApiaryHistory_apiaryId_fkey" FOREIGN KEY ("apiaryId") REFERENCES "Apiary"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ColonyApiaryHistory" ADD CONSTRAINT "ColonyApiaryHistory_movedByUserId_fkey" FOREIGN KEY ("movedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ColonyLineage" ADD CONSTRAINT "ColonyLineage_fromColonyId_fkey" FOREIGN KEY ("fromColonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ColonyLineage" ADD CONSTRAINT "ColonyLineage_toColonyId_fkey" FOREIGN KEY ("toColonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QueenColonyHistory" ADD CONSTRAINT "QueenColonyHistory_queenId_fkey" FOREIGN KEY ("queenId") REFERENCES "Queen"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "QueenColonyHistory" ADD CONSTRAINT "QueenColonyHistory_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Inspection" ADD CONSTRAINT "Inspection_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Inspection" ADD CONSTRAINT "Inspection_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_assignedUserId_fkey" FOREIGN KEY ("assignedUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_sourceInspectionId_fkey" FOREIGN KEY ("sourceInspectionId") REFERENCES "Inspection"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VarroaMeasurement" ADD CONSTRAINT "VarroaMeasurement_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VarroaMeasurement" ADD CONSTRAINT "VarroaMeasurement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Treatment" ADD CONSTRAINT "Treatment_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Treatment" ADD CONSTRAINT "Treatment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Feeding" ADD CONSTRAINT "Feeding_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Feeding" ADD CONSTRAINT "Feeding_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Harvest" ADD CONSTRAINT "Harvest_apiaryId_fkey" FOREIGN KEY ("apiaryId") REFERENCES "Apiary"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Harvest" ADD CONSTRAINT "Harvest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HarvestColony" ADD CONSTRAINT "HarvestColony_harvestId_fkey" FOREIGN KEY ("harvestId") REFERENCES "Harvest"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HarvestColony" ADD CONSTRAINT "HarvestColony_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Extraction" ADD CONSTRAINT "Extraction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ExtractionHarvest" ADD CONSTRAINT "ExtractionHarvest_extractionId_fkey" FOREIGN KEY ("extractionId") REFERENCES "Extraction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ExtractionHarvest" ADD CONSTRAINT "ExtractionHarvest_harvestId_fkey" FOREIGN KEY ("harvestId") REFERENCES "Harvest"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HoneyBatch" ADD CONSTRAINT "HoneyBatch_extractionId_fkey" FOREIGN KEY ("extractionId") REFERENCES "Extraction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HoneyBatch" ADD CONSTRAINT "HoneyBatch_storageContainerId_fkey" FOREIGN KEY ("storageContainerId") REFERENCES "StorageContainer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HoneyMeasurement" ADD CONSTRAINT "HoneyMeasurement_honeyBatchId_fkey" FOREIGN KEY ("honeyBatchId") REFERENCES "HoneyBatch"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HoneyMeasurement" ADD CONSTRAINT "HoneyMeasurement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockMovement" ADD CONSTRAINT "StockMovement_stockItemId_fkey" FOREIGN KEY ("stockItemId") REFERENCES "StockItem"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockMovement" ADD CONSTRAINT "StockMovement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -4,7 +4,7 @@
|
||||
// keine Freitextfelder, damit die Rückverfolgbarkeitskette (Abschnitt 16, 29) abfragbar bleibt.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
provider = "prisma-client-js"
|
||||
output = "../generated/prisma"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { HealthModule } from './health/health.module';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
PrismaModule,
|
||||
HealthModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ServiceUnavailableException } from '@nestjs/common';
|
||||
import { HealthController } from './health.controller';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
describe('HealthController', () => {
|
||||
let controller: HealthController;
|
||||
let prisma: { $queryRaw: jest.Mock };
|
||||
|
||||
beforeEach(() => {
|
||||
prisma = { $queryRaw: jest.fn() };
|
||||
controller = new HealthController(prisma as unknown as PrismaService);
|
||||
});
|
||||
|
||||
it('liveness returns ok without touching the database', () => {
|
||||
expect(controller.liveness()).toEqual({ status: 'ok' });
|
||||
expect(prisma.$queryRaw).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('readiness returns ok when the database responds', async () => {
|
||||
prisma.$queryRaw.mockResolvedValueOnce([{ '?column?': 1 }]);
|
||||
await expect(controller.readiness()).resolves.toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
it('readiness throws ServiceUnavailableException when the database is unreachable', async () => {
|
||||
prisma.$queryRaw.mockRejectedValueOnce(new Error('connection refused'));
|
||||
await expect(controller.readiness()).rejects.toBeInstanceOf(
|
||||
ServiceUnavailableException,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
ServiceUnavailableException,
|
||||
VERSION_NEUTRAL,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@ApiTags('health')
|
||||
@Controller({ path: 'health', version: VERSION_NEUTRAL })
|
||||
export class HealthController {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
@Get()
|
||||
liveness(): { status: 'ok' } {
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
@Get('ready')
|
||||
async readiness(): Promise<{ status: 'ok' }> {
|
||||
try {
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
return { status: 'ok' };
|
||||
} catch {
|
||||
throw new ServiceUnavailableException('Database not reachable');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class HealthModule {}
|
||||
@@ -1,8 +1,39 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { RequestMethod, ValidationPipe, VersioningType } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import helmet from 'helmet';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.use(helmet());
|
||||
|
||||
app.setGlobalPrefix('api', {
|
||||
exclude: [
|
||||
{ path: 'health', method: RequestMethod.GET },
|
||||
{ path: 'health/ready', method: RequestMethod.GET },
|
||||
],
|
||||
});
|
||||
app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' });
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const swaggerConfig = new DocumentBuilder()
|
||||
.setTitle('ApiCore API')
|
||||
.setDescription('Digitale Imkereiverwaltung – API-Dokumentation')
|
||||
.setVersion('1.0')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
|
||||
SwaggerModule.setup('api/v1/docs', app, swaggerDocument);
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
void bootstrap();
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import { PrismaClient } from '../../generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService
|
||||
extends PrismaClient
|
||||
implements OnModuleInit, OnModuleDestroy
|
||||
{
|
||||
constructor() {
|
||||
super({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) });
|
||||
}
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "prisma.config.ts"]
|
||||
}
|
||||
Reference in new issue
Block a user