KI-AGENT: Zentralen Push-Server Stack ergänzen
This commit is contained in:
10
push-server/packages/db/drizzle.config.ts
Normal file
10
push-server/packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/schema.ts",
|
||||
out: "./drizzle",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || "postgres://fedeo_push:fedeo_push@localhost:5442/fedeo_push",
|
||||
},
|
||||
});
|
||||
106
push-server/packages/db/drizzle/0000_big_devos.sql
Normal file
106
push-server/packages/db/drizzle/0000_big_devos.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
CREATE TYPE "public"."attempt_provider" AS ENUM('web_push', 'apns', 'fcm');--> statement-breakpoint
|
||||
CREATE TYPE "public"."attempt_status" AS ENUM('pending', 'sent', 'failed', 'skipped');--> statement-breakpoint
|
||||
CREATE TYPE "public"."delivery_status" AS ENUM('accepted', 'processing', 'completed', 'failed', 'partial');--> statement-breakpoint
|
||||
CREATE TYPE "public"."device_platform" AS ENUM('web', 'ios', 'android');--> statement-breakpoint
|
||||
CREATE TYPE "public"."device_status" AS ENUM('active', 'disabled', 'invalid');--> statement-breakpoint
|
||||
CREATE TYPE "public"."instance_status" AS ENUM('active', 'blocked', 'disabled');--> statement-breakpoint
|
||||
CREATE TYPE "public"."payload_mode" AS ENUM('minimal', 'rich');--> statement-breakpoint
|
||||
CREATE TABLE "audit_logs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"actor" text NOT NULL,
|
||||
"action" text NOT NULL,
|
||||
"instance_id" uuid,
|
||||
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "delivery_attempts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"delivery_job_id" uuid NOT NULL,
|
||||
"device_id" uuid NOT NULL,
|
||||
"provider" "attempt_provider" NOT NULL,
|
||||
"status" "attempt_status" DEFAULT 'pending' NOT NULL,
|
||||
"provider_message_id" text,
|
||||
"error_code" text,
|
||||
"error_message" text,
|
||||
"sent_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "delivery_jobs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"delivery_job_id" text NOT NULL,
|
||||
"instance_id" uuid NOT NULL,
|
||||
"idempotency_key" text NOT NULL,
|
||||
"status" "delivery_status" DEFAULT 'accepted' NOT NULL,
|
||||
"priority" text DEFAULT 'normal' NOT NULL,
|
||||
"collapse_key" text,
|
||||
"ttl_seconds" integer DEFAULT 3600 NOT NULL,
|
||||
"accepted_count" integer DEFAULT 0 NOT NULL,
|
||||
"rejected_count" integer DEFAULT 0 NOT NULL,
|
||||
"sent_count" integer DEFAULT 0 NOT NULL,
|
||||
"failed_count" integer DEFAULT 0 NOT NULL,
|
||||
"last_error_code" text,
|
||||
"last_error_message" text,
|
||||
"completed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "delivery_jobs_delivery_job_id_unique" UNIQUE("delivery_job_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "push_devices" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"central_device_id" text NOT NULL,
|
||||
"instance_id" uuid NOT NULL,
|
||||
"local_device_id" text NOT NULL,
|
||||
"platform" "device_platform" NOT NULL,
|
||||
"status" "device_status" DEFAULT 'active' NOT NULL,
|
||||
"provider_token_encrypted" text,
|
||||
"web_push_subscription" jsonb,
|
||||
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"last_seen_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"disabled_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "push_devices_central_device_id_unique" UNIQUE("central_device_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "push_instances" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"instance_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"base_url" text NOT NULL,
|
||||
"status" "instance_status" DEFAULT 'active' NOT NULL,
|
||||
"payload_mode" "payload_mode" DEFAULT 'minimal' NOT NULL,
|
||||
"capabilities" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"rate_limit_per_minute" integer DEFAULT 120 NOT NULL,
|
||||
"daily_quota" integer DEFAULT 10000 NOT NULL,
|
||||
"current_secret_encrypted" text NOT NULL,
|
||||
"current_secret_preview" text NOT NULL,
|
||||
"next_secret_encrypted" text,
|
||||
"next_secret_preview" text,
|
||||
"notes" text,
|
||||
"last_heartbeat_at" timestamp with time zone,
|
||||
"last_heartbeat_version" text,
|
||||
"last_heartbeat_ip" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "push_instances_instance_id_unique" UNIQUE("instance_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_instance_id_push_instances_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."push_instances"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "delivery_attempts" ADD CONSTRAINT "delivery_attempts_delivery_job_id_delivery_jobs_id_fk" FOREIGN KEY ("delivery_job_id") REFERENCES "public"."delivery_jobs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "delivery_attempts" ADD CONSTRAINT "delivery_attempts_device_id_push_devices_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."push_devices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "delivery_jobs" ADD CONSTRAINT "delivery_jobs_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 "push_devices" ADD CONSTRAINT "push_devices_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 INDEX "audit_logs_instance_created_idx" ON "audit_logs" USING btree ("instance_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "delivery_attempts_job_idx" ON "delivery_attempts" USING btree ("delivery_job_id");--> statement-breakpoint
|
||||
CREATE INDEX "delivery_attempts_device_idx" ON "delivery_attempts" USING btree ("device_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "delivery_jobs_delivery_job_id_idx" ON "delivery_jobs" USING btree ("delivery_job_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "delivery_jobs_instance_idempotency_idx" ON "delivery_jobs" USING btree ("instance_id","idempotency_key");--> statement-breakpoint
|
||||
CREATE INDEX "delivery_jobs_instance_created_idx" ON "delivery_jobs" USING btree ("instance_id","created_at");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "push_devices_central_device_id_idx" ON "push_devices" USING btree ("central_device_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "push_devices_instance_local_device_idx" ON "push_devices" USING btree ("instance_id","local_device_id");--> statement-breakpoint
|
||||
CREATE INDEX "push_devices_instance_status_idx" ON "push_devices" USING btree ("instance_id","status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "push_instances_instance_id_idx" ON "push_instances" USING btree ("instance_id");--> statement-breakpoint
|
||||
CREATE INDEX "push_instances_status_idx" ON "push_instances" USING btree ("status");
|
||||
870
push-server/packages/db/drizzle/meta/0000_snapshot.json
Normal file
870
push-server/packages/db/drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,870 @@
|
||||
{
|
||||
"id": "bcd8b541-814a-4dfb-8cb8-b677d1218da4",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.audit_logs": {
|
||||
"name": "audit_logs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"actor": {
|
||||
"name": "actor",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"action": {
|
||||
"name": "action",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"instance_id": {
|
||||
"name": "instance_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"meta": {
|
||||
"name": "meta",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"audit_logs_instance_created_idx": {
|
||||
"name": "audit_logs_instance_created_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "instance_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"audit_logs_instance_id_push_instances_id_fk": {
|
||||
"name": "audit_logs_instance_id_push_instances_id_fk",
|
||||
"tableFrom": "audit_logs",
|
||||
"tableTo": "push_instances",
|
||||
"columnsFrom": [
|
||||
"instance_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.delivery_attempts": {
|
||||
"name": "delivery_attempts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"delivery_job_id": {
|
||||
"name": "delivery_job_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"device_id": {
|
||||
"name": "device_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "attempt_provider",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "attempt_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"provider_message_id": {
|
||||
"name": "provider_message_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"error_code": {
|
||||
"name": "error_code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"error_message": {
|
||||
"name": "error_message",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sent_at": {
|
||||
"name": "sent_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"delivery_attempts_job_idx": {
|
||||
"name": "delivery_attempts_job_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "delivery_job_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"delivery_attempts_device_idx": {
|
||||
"name": "delivery_attempts_device_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "device_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"delivery_attempts_delivery_job_id_delivery_jobs_id_fk": {
|
||||
"name": "delivery_attempts_delivery_job_id_delivery_jobs_id_fk",
|
||||
"tableFrom": "delivery_attempts",
|
||||
"tableTo": "delivery_jobs",
|
||||
"columnsFrom": [
|
||||
"delivery_job_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"delivery_attempts_device_id_push_devices_id_fk": {
|
||||
"name": "delivery_attempts_device_id_push_devices_id_fk",
|
||||
"tableFrom": "delivery_attempts",
|
||||
"tableTo": "push_devices",
|
||||
"columnsFrom": [
|
||||
"device_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.delivery_jobs": {
|
||||
"name": "delivery_jobs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"delivery_job_id": {
|
||||
"name": "delivery_job_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"instance_id": {
|
||||
"name": "instance_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"idempotency_key": {
|
||||
"name": "idempotency_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "delivery_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'accepted'"
|
||||
},
|
||||
"priority": {
|
||||
"name": "priority",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'normal'"
|
||||
},
|
||||
"collapse_key": {
|
||||
"name": "collapse_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ttl_seconds": {
|
||||
"name": "ttl_seconds",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 3600
|
||||
},
|
||||
"accepted_count": {
|
||||
"name": "accepted_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"rejected_count": {
|
||||
"name": "rejected_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"sent_count": {
|
||||
"name": "sent_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"failed_count": {
|
||||
"name": "failed_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"last_error_code": {
|
||||
"name": "last_error_code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_error_message": {
|
||||
"name": "last_error_message",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"delivery_jobs_delivery_job_id_idx": {
|
||||
"name": "delivery_jobs_delivery_job_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "delivery_job_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"delivery_jobs_instance_idempotency_idx": {
|
||||
"name": "delivery_jobs_instance_idempotency_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "instance_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "idempotency_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"delivery_jobs_instance_created_idx": {
|
||||
"name": "delivery_jobs_instance_created_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "instance_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"delivery_jobs_instance_id_push_instances_id_fk": {
|
||||
"name": "delivery_jobs_instance_id_push_instances_id_fk",
|
||||
"tableFrom": "delivery_jobs",
|
||||
"tableTo": "push_instances",
|
||||
"columnsFrom": [
|
||||
"instance_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"delivery_jobs_delivery_job_id_unique": {
|
||||
"name": "delivery_jobs_delivery_job_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"delivery_job_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.push_devices": {
|
||||
"name": "push_devices",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"central_device_id": {
|
||||
"name": "central_device_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"instance_id": {
|
||||
"name": "instance_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"local_device_id": {
|
||||
"name": "local_device_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"platform": {
|
||||
"name": "platform",
|
||||
"type": "device_platform",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "device_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'active'"
|
||||
},
|
||||
"provider_token_encrypted": {
|
||||
"name": "provider_token_encrypted",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"web_push_subscription": {
|
||||
"name": "web_push_subscription",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"meta": {
|
||||
"name": "meta",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"last_seen_at": {
|
||||
"name": "last_seen_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"disabled_at": {
|
||||
"name": "disabled_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"push_devices_central_device_id_idx": {
|
||||
"name": "push_devices_central_device_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "central_device_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"push_devices_instance_local_device_idx": {
|
||||
"name": "push_devices_instance_local_device_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "instance_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "local_device_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"push_devices_instance_status_idx": {
|
||||
"name": "push_devices_instance_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "instance_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"push_devices_instance_id_push_instances_id_fk": {
|
||||
"name": "push_devices_instance_id_push_instances_id_fk",
|
||||
"tableFrom": "push_devices",
|
||||
"tableTo": "push_instances",
|
||||
"columnsFrom": [
|
||||
"instance_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"push_devices_central_device_id_unique": {
|
||||
"name": "push_devices_central_device_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"central_device_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.push_instances": {
|
||||
"name": "push_instances",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"instance_id": {
|
||||
"name": "instance_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"base_url": {
|
||||
"name": "base_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "instance_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'active'"
|
||||
},
|
||||
"payload_mode": {
|
||||
"name": "payload_mode",
|
||||
"type": "payload_mode",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'minimal'"
|
||||
},
|
||||
"capabilities": {
|
||||
"name": "capabilities",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"rate_limit_per_minute": {
|
||||
"name": "rate_limit_per_minute",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 120
|
||||
},
|
||||
"daily_quota": {
|
||||
"name": "daily_quota",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 10000
|
||||
},
|
||||
"current_secret_encrypted": {
|
||||
"name": "current_secret_encrypted",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"current_secret_preview": {
|
||||
"name": "current_secret_preview",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"next_secret_encrypted": {
|
||||
"name": "next_secret_encrypted",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"next_secret_preview": {
|
||||
"name": "next_secret_preview",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_heartbeat_at": {
|
||||
"name": "last_heartbeat_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_heartbeat_version": {
|
||||
"name": "last_heartbeat_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_heartbeat_ip": {
|
||||
"name": "last_heartbeat_ip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"push_instances_instance_id_idx": {
|
||||
"name": "push_instances_instance_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "instance_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"push_instances_status_idx": {
|
||||
"name": "push_instances_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"push_instances_instance_id_unique": {
|
||||
"name": "push_instances_instance_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"instance_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.attempt_provider": {
|
||||
"name": "attempt_provider",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"web_push",
|
||||
"apns",
|
||||
"fcm"
|
||||
]
|
||||
},
|
||||
"public.attempt_status": {
|
||||
"name": "attempt_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"pending",
|
||||
"sent",
|
||||
"failed",
|
||||
"skipped"
|
||||
]
|
||||
},
|
||||
"public.delivery_status": {
|
||||
"name": "delivery_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"accepted",
|
||||
"processing",
|
||||
"completed",
|
||||
"failed",
|
||||
"partial"
|
||||
]
|
||||
},
|
||||
"public.device_platform": {
|
||||
"name": "device_platform",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"web",
|
||||
"ios",
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"public.device_status": {
|
||||
"name": "device_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"active",
|
||||
"disabled",
|
||||
"invalid"
|
||||
]
|
||||
},
|
||||
"public.instance_status": {
|
||||
"name": "instance_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"active",
|
||||
"blocked",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"public.payload_mode": {
|
||||
"name": "payload_mode",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"minimal",
|
||||
"rich"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
13
push-server/packages/db/drizzle/meta/_journal.json
Normal file
13
push-server/packages/db/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1779461560095,
|
||||
"tag": "0000_big_devos",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
22
push-server/packages/db/package.json
Normal file
22
push-server/packages/db/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@fedeo/push-db",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"drizzle-orm": "^0.45.0",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/pg": "^8.15.5",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
1
push-server/packages/db/src/index.ts
Normal file
1
push-server/packages/db/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./schema.js";
|
||||
154
push-server/packages/db/src/schema.ts
Normal file
154
push-server/packages/db/src/schema.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
export const instanceStatus = pgEnum("instance_status", ["active", "blocked", "disabled"]);
|
||||
export const payloadMode = pgEnum("payload_mode", ["minimal", "rich"]);
|
||||
export const devicePlatform = pgEnum("device_platform", ["web", "ios", "android"]);
|
||||
export const deviceStatus = pgEnum("device_status", ["active", "disabled", "invalid"]);
|
||||
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 pushInstances = pgTable(
|
||||
"push_instances",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
instanceId: text("instance_id").notNull().unique(),
|
||||
name: text("name").notNull(),
|
||||
baseUrl: text("base_url").notNull(),
|
||||
status: instanceStatus("status").notNull().default("active"),
|
||||
mode: payloadMode("payload_mode").notNull().default("minimal"),
|
||||
capabilities: jsonb("capabilities").$type<string[]>().notNull().default(sql`'[]'::jsonb`),
|
||||
rateLimitPerMinute: integer("rate_limit_per_minute").notNull().default(120),
|
||||
dailyQuota: integer("daily_quota").notNull().default(10000),
|
||||
currentSecretEncrypted: text("current_secret_encrypted").notNull(),
|
||||
currentSecretPreview: text("current_secret_preview").notNull(),
|
||||
nextSecretEncrypted: text("next_secret_encrypted"),
|
||||
nextSecretPreview: text("next_secret_preview"),
|
||||
notes: text("notes"),
|
||||
lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }),
|
||||
lastHeartbeatVersion: text("last_heartbeat_version"),
|
||||
lastHeartbeatIp: text("last_heartbeat_ip"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
instanceIdIdx: uniqueIndex("push_instances_instance_id_idx").on(table.instanceId),
|
||||
statusIdx: index("push_instances_status_idx").on(table.status),
|
||||
}),
|
||||
);
|
||||
|
||||
export const pushDevices = pgTable(
|
||||
"push_devices",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
centralDeviceId: text("central_device_id").notNull().unique(),
|
||||
instanceId: uuid("instance_id")
|
||||
.notNull()
|
||||
.references(() => pushInstances.id, { onDelete: "cascade" }),
|
||||
localDeviceId: text("local_device_id").notNull(),
|
||||
platform: devicePlatform("platform").notNull(),
|
||||
status: deviceStatus("status").notNull().default("active"),
|
||||
providerTokenEncrypted: text("provider_token_encrypted"),
|
||||
webPushSubscription: jsonb("web_push_subscription").$type<Record<string, unknown>>(),
|
||||
meta: jsonb("meta").$type<Record<string, unknown>>().notNull().default(sql`'{}'::jsonb`),
|
||||
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
disabledAt: timestamp("disabled_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
centralDeviceIdIdx: uniqueIndex("push_devices_central_device_id_idx").on(table.centralDeviceId),
|
||||
instanceLocalDeviceIdx: uniqueIndex("push_devices_instance_local_device_idx").on(table.instanceId, table.localDeviceId),
|
||||
instanceStatusIdx: index("push_devices_instance_status_idx").on(table.instanceId, table.status),
|
||||
}),
|
||||
);
|
||||
|
||||
export const deliveryJobs = pgTable(
|
||||
"delivery_jobs",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
deliveryJobId: text("delivery_job_id").notNull().unique(),
|
||||
instanceId: uuid("instance_id")
|
||||
.notNull()
|
||||
.references(() => pushInstances.id, { onDelete: "cascade" }),
|
||||
idempotencyKey: text("idempotency_key").notNull(),
|
||||
status: deliveryStatus("status").notNull().default("accepted"),
|
||||
priority: text("priority").notNull().default("normal"),
|
||||
collapseKey: text("collapse_key"),
|
||||
ttlSeconds: integer("ttl_seconds").notNull().default(3600),
|
||||
acceptedCount: integer("accepted_count").notNull().default(0),
|
||||
rejectedCount: integer("rejected_count").notNull().default(0),
|
||||
sentCount: integer("sent_count").notNull().default(0),
|
||||
failedCount: integer("failed_count").notNull().default(0),
|
||||
lastErrorCode: text("last_error_code"),
|
||||
lastErrorMessage: text("last_error_message"),
|
||||
completedAt: timestamp("completed_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
deliveryJobIdIdx: uniqueIndex("delivery_jobs_delivery_job_id_idx").on(table.deliveryJobId),
|
||||
instanceIdempotencyIdx: uniqueIndex("delivery_jobs_instance_idempotency_idx").on(table.instanceId, table.idempotencyKey),
|
||||
instanceCreatedIdx: index("delivery_jobs_instance_created_idx").on(table.instanceId, table.createdAt),
|
||||
}),
|
||||
);
|
||||
|
||||
export const deliveryAttempts = pgTable(
|
||||
"delivery_attempts",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
deliveryJobId: uuid("delivery_job_id")
|
||||
.notNull()
|
||||
.references(() => deliveryJobs.id, { onDelete: "cascade" }),
|
||||
deviceId: uuid("device_id")
|
||||
.notNull()
|
||||
.references(() => pushDevices.id, { onDelete: "cascade" }),
|
||||
provider: attemptProvider("provider").notNull(),
|
||||
status: attemptStatus("status").notNull().default("pending"),
|
||||
providerMessageId: text("provider_message_id"),
|
||||
errorCode: text("error_code"),
|
||||
errorMessage: text("error_message"),
|
||||
sentAt: timestamp("sent_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
jobIdx: index("delivery_attempts_job_idx").on(table.deliveryJobId),
|
||||
deviceIdx: index("delivery_attempts_device_idx").on(table.deviceId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const auditLogs = pgTable(
|
||||
"audit_logs",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
actor: text("actor").notNull(),
|
||||
action: text("action").notNull(),
|
||||
instanceId: uuid("instance_id").references(() => pushInstances.id, { onDelete: "set null" }),
|
||||
meta: jsonb("meta").$type<Record<string, unknown>>().notNull().default(sql`'{}'::jsonb`),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
instanceCreatedIdx: index("audit_logs_instance_created_idx").on(table.instanceId, table.createdAt),
|
||||
}),
|
||||
);
|
||||
|
||||
export type PushInstance = typeof pushInstances.$inferSelect;
|
||||
export type NewPushInstance = typeof pushInstances.$inferInsert;
|
||||
export type PushDevice = typeof pushDevices.$inferSelect;
|
||||
export type NewPushDevice = typeof pushDevices.$inferInsert;
|
||||
export type DeliveryJob = typeof deliveryJobs.$inferSelect;
|
||||
export type NewDeliveryJob = typeof deliveryJobs.$inferInsert;
|
||||
export type DeliveryAttempt = typeof deliveryAttempts.$inferSelect;
|
||||
export type NewDeliveryAttempt = typeof deliveryAttempts.$inferInsert;
|
||||
14
push-server/packages/db/tsconfig.json
Normal file
14
push-server/packages/db/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts", "drizzle.config.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user