feat: zentrale selfhost-dienste bereitstellen

This commit is contained in:
2026-08-02 16:34:10 +02:00
parent d3ad53bcf0
commit 8b8e0c97d3
30 changed files with 1818 additions and 74 deletions

View File

@@ -0,0 +1,36 @@
CREATE TYPE "public"."central_service" AS ENUM('ai', 'banking');--> statement-breakpoint
CREATE TYPE "public"."usage_status" AS ENUM('succeeded', 'failed');--> statement-breakpoint
CREATE TABLE "service_entitlements" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"instance_id" uuid NOT NULL,
"service" "central_service" NOT NULL,
"enabled" boolean DEFAULT false NOT NULL,
"monthly_limit" bigint,
"unit_price_micros" bigint DEFAULT 0 NOT NULL,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "service_usage_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"request_id" text NOT NULL,
"instance_id" uuid NOT NULL,
"service" "central_service" NOT NULL,
"operation" text NOT NULL,
"units" bigint DEFAULT 1 NOT NULL,
"cost_micros" bigint DEFAULT 0 NOT NULL,
"status" "usage_status" NOT NULL,
"provider" text,
"provider_request_id" text,
"error_code" text,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "service_usage_events_request_id_unique" UNIQUE("request_id")
);
--> statement-breakpoint
ALTER TABLE "service_entitlements" ADD CONSTRAINT "service_entitlements_instance_id_push_instances_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."push_instances"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "service_usage_events" ADD CONSTRAINT "service_usage_events_instance_id_push_instances_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."push_instances"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "service_entitlements_instance_service_idx" ON "service_entitlements" USING btree ("instance_id","service");--> statement-breakpoint
CREATE UNIQUE INDEX "service_usage_events_request_id_idx" ON "service_usage_events" USING btree ("request_id");--> statement-breakpoint
CREATE INDEX "service_usage_events_instance_service_created_idx" ON "service_usage_events" USING btree ("instance_id","service","created_at");

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,13 @@
"when": 1779461560095,
"tag": "0000_big_devos",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1785681122775,
"tag": "0001_central_services",
"breakpoints": true
}
]
}

View File

@@ -3,8 +3,10 @@
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"main": "dist/src/index.js",
"types": "src/index.ts",
"scripts": {
"build": "tsc",
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx src/migrate.ts",
"typecheck": "tsc --noEmit"

View File

@@ -8,7 +8,9 @@ import pg from "pg";
const { Pool } = pg;
const databaseUrl = process.env.DATABASE_URL || "postgres://fedeo_push:fedeo_push@localhost:5442/fedeo_push";
const migrationsFolder = resolve(dirname(fileURLToPath(import.meta.url)), "../drizzle");
const migrationsFolder = process.env.DRIZZLE_MIGRATIONS_FOLDER
? resolve(process.env.DRIZZLE_MIGRATIONS_FOLDER)
: resolve(dirname(fileURLToPath(import.meta.url)), "../drizzle");
const pool = new Pool({ connectionString: databaseUrl });
const db = drizzle(pool);

View File

@@ -1,5 +1,6 @@
import {
boolean,
bigint,
index,
integer,
jsonb,
@@ -19,6 +20,8 @@ export const deviceStatus = pgEnum("device_status", ["active", "disabled", "inva
export const deliveryStatus = pgEnum("delivery_status", ["accepted", "processing", "completed", "failed", "partial"]);
export const attemptStatus = pgEnum("attempt_status", ["pending", "sent", "failed", "skipped"]);
export const attemptProvider = pgEnum("attempt_provider", ["web_push", "apns", "fcm"]);
export const centralService = pgEnum("central_service", ["ai", "banking"]);
export const usageStatus = pgEnum("usage_status", ["succeeded", "failed"]);
export const pushInstances = pgTable(
"push_instances",
@@ -144,6 +147,51 @@ export const auditLogs = pgTable(
}),
);
export const serviceEntitlements = pgTable(
"service_entitlements",
{
id: uuid("id").primaryKey().defaultRandom(),
instanceId: uuid("instance_id")
.notNull()
.references(() => pushInstances.id, { onDelete: "cascade" }),
service: centralService("service").notNull(),
enabled: boolean("enabled").notNull().default(false),
monthlyLimit: bigint("monthly_limit", { mode: "number" }),
unitPriceMicros: bigint("unit_price_micros", { mode: "number" }).notNull().default(0),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default(sql`'{}'::jsonb`),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
instanceServiceIdx: uniqueIndex("service_entitlements_instance_service_idx").on(table.instanceId, table.service),
}),
);
export const serviceUsageEvents = pgTable(
"service_usage_events",
{
id: uuid("id").primaryKey().defaultRandom(),
requestId: text("request_id").notNull().unique(),
instanceId: uuid("instance_id")
.notNull()
.references(() => pushInstances.id, { onDelete: "cascade" }),
service: centralService("service").notNull(),
operation: text("operation").notNull(),
units: bigint("units", { mode: "number" }).notNull().default(1),
costMicros: bigint("cost_micros", { mode: "number" }).notNull().default(0),
status: usageStatus("status").notNull(),
provider: text("provider"),
providerRequestId: text("provider_request_id"),
errorCode: text("error_code"),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default(sql`'{}'::jsonb`),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
requestIdIdx: uniqueIndex("service_usage_events_request_id_idx").on(table.requestId),
instanceServiceCreatedIdx: index("service_usage_events_instance_service_created_idx").on(table.instanceId, table.service, table.createdAt),
}),
);
export type PushInstance = typeof pushInstances.$inferSelect;
export type NewPushInstance = typeof pushInstances.$inferInsert;
export type PushDevice = typeof pushDevices.$inferSelect;
@@ -152,3 +200,5 @@ export type DeliveryJob = typeof deliveryJobs.$inferSelect;
export type NewDeliveryJob = typeof deliveryJobs.$inferInsert;
export type DeliveryAttempt = typeof deliveryAttempts.$inferSelect;
export type NewDeliveryAttempt = typeof deliveryAttempts.$inferInsert;
export type ServiceEntitlement = typeof serviceEntitlements.$inferSelect;
export type ServiceUsageEvent = typeof serviceUsageEvents.$inferSelect;