diff --git a/.env.example b/.env.example index 5b6202b..908ef6f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ -DATABASE_URL=postgres://tessera:password@localhost:5432/tessera +DATABASE_URL=postgres://tessera:tessera@127.0.0.1:5435/tessera SERVER_HOST=127.0.0.1 SERVER_PORT=9876 diff --git a/.gitignore b/.gitignore index 2e1a70f..e26a5f3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ bun.lock # Codegraph index (MCP tool) .codegraph + +# Runtime data +/data diff --git a/CLAUDE.md b/CLAUDE.md index cf8c4db..cbb6a16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ tessera/ - Bun (`nix-shell -p bun` or install globally) - Node.js 22+ (`nix-shell -p nodejs_22`) - Docker (for PostgreSQL) -- PostgreSQL container: `docker run -d --name tessera-db -e POSTGRES_USER=tessera -e POSTGRES_PASSWORD=*** -e POSTGRES_DB=tessera -p 127.0.0.1:5433:5432 postgres:17-alpine` +- PostgreSQL container: `docker run -d --name tessera-db -e POSTGRES_USER=tessera -e POSTGRES_PASSWORD=tessera -e POSTGRES_DB=tessera -p 127.0.0.1:5435:5432 postgres:17-alpine` ### Start backend ```bash diff --git a/drizzle/migrations/0008_sturdy_prism.sql b/drizzle/migrations/0008_sturdy_prism.sql new file mode 100644 index 0000000..6d96e3f --- /dev/null +++ b/drizzle/migrations/0008_sturdy_prism.sql @@ -0,0 +1,12 @@ +CREATE TABLE "transaction_attachments" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "transaction_id" uuid NOT NULL, + "filename" text NOT NULL, + "mime_type" text DEFAULT 'application/octet-stream' NOT NULL, + "size_bytes" integer DEFAULT 0 NOT NULL, + "storage_path" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() +); +--> statement-breakpoint +ALTER TABLE "transaction_attachments" ADD CONSTRAINT "transaction_attachments_transaction_id_transactions_id_fk" FOREIGN KEY ("transaction_id") REFERENCES "public"."transactions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "transaction_attachments_tx_id_idx" ON "transaction_attachments" USING btree ("transaction_id"); \ No newline at end of file diff --git a/drizzle/migrations/0009_tiny_lady_vermin.sql b/drizzle/migrations/0009_tiny_lady_vermin.sql new file mode 100644 index 0000000..a9a735e --- /dev/null +++ b/drizzle/migrations/0009_tiny_lady_vermin.sql @@ -0,0 +1 @@ +ALTER TABLE "transaction_attachments" ALTER COLUMN "transaction_id" DROP NOT NULL; \ No newline at end of file diff --git a/drizzle/migrations/0010_misty_morg.sql b/drizzle/migrations/0010_misty_morg.sql new file mode 100644 index 0000000..da92b12 --- /dev/null +++ b/drizzle/migrations/0010_misty_morg.sql @@ -0,0 +1,15 @@ +CREATE TABLE "ticket_links" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "ticket_id" integer NOT NULL, + "target_ticket_id" integer NOT NULL, + "link_type" text NOT NULL, + "creator_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now(), + CONSTRAINT "ticket_links_ticket_target_type_unique" UNIQUE("ticket_id","target_ticket_id","link_type") +); +--> statement-breakpoint +ALTER TABLE "ticket_links" ADD CONSTRAINT "ticket_links_ticket_id_tickets_id_fk" FOREIGN KEY ("ticket_id") REFERENCES "public"."tickets"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ticket_links" ADD CONSTRAINT "ticket_links_target_ticket_id_tickets_id_fk" FOREIGN KEY ("target_ticket_id") REFERENCES "public"."tickets"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ticket_links" ADD CONSTRAINT "ticket_links_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "ticket_links_ticket_id_idx" ON "ticket_links" USING btree ("ticket_id");--> statement-breakpoint +CREATE INDEX "ticket_links_target_ticket_id_idx" ON "ticket_links" USING btree ("target_ticket_id"); \ No newline at end of file diff --git a/drizzle/migrations/0011_breezy_tyrannus.sql b/drizzle/migrations/0011_breezy_tyrannus.sql new file mode 100644 index 0000000..5c7271e --- /dev/null +++ b/drizzle/migrations/0011_breezy_tyrannus.sql @@ -0,0 +1,2 @@ +ALTER TABLE "users" ADD COLUMN "password_hash" text;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "role" text DEFAULT 'staff' NOT NULL; \ No newline at end of file diff --git a/drizzle/migrations/0012_living_photon.sql b/drizzle/migrations/0012_living_photon.sql new file mode 100644 index 0000000..4b3ed7e --- /dev/null +++ b/drizzle/migrations/0012_living_photon.sql @@ -0,0 +1,12 @@ +CREATE TABLE "queue_permissions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "queue_id" uuid NOT NULL, + "team_id" uuid NOT NULL, + "right_name" text NOT NULL, + CONSTRAINT "queue_permissions_queue_team_right_unique" UNIQUE("queue_id","team_id","right_name") +); +--> statement-breakpoint +ALTER TABLE "queue_permissions" ADD CONSTRAINT "queue_permissions_queue_id_queues_id_fk" FOREIGN KEY ("queue_id") REFERENCES "public"."queues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "queue_permissions" ADD CONSTRAINT "queue_permissions_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "queue_permissions_queue_id_idx" ON "queue_permissions" USING btree ("queue_id");--> statement-breakpoint +CREATE INDEX "queue_permissions_team_id_idx" ON "queue_permissions" USING btree ("team_id"); \ No newline at end of file diff --git a/drizzle/migrations/0013_bored_silvermane.sql b/drizzle/migrations/0013_bored_silvermane.sql new file mode 100644 index 0000000..e9c4153 --- /dev/null +++ b/drizzle/migrations/0013_bored_silvermane.sql @@ -0,0 +1,12 @@ +CREATE TABLE "user_permissions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "queue_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "right_name" text NOT NULL, + CONSTRAINT "user_permissions_queue_user_right_unique" UNIQUE("queue_id","user_id","right_name") +); +--> statement-breakpoint +ALTER TABLE "user_permissions" ADD CONSTRAINT "user_permissions_queue_id_queues_id_fk" FOREIGN KEY ("queue_id") REFERENCES "public"."queues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "user_permissions" ADD CONSTRAINT "user_permissions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "user_permissions_queue_id_idx" ON "user_permissions" USING btree ("queue_id");--> statement-breakpoint +CREATE INDEX "user_permissions_user_id_idx" ON "user_permissions" USING btree ("user_id"); \ No newline at end of file diff --git a/drizzle/migrations/0014_cloudy_siren.sql b/drizzle/migrations/0014_cloudy_siren.sql new file mode 100644 index 0000000..f2a88c4 --- /dev/null +++ b/drizzle/migrations/0014_cloudy_siren.sql @@ -0,0 +1 @@ +ALTER TABLE "transactions" ADD COLUMN "time_worked_minutes" integer DEFAULT 0; \ No newline at end of file diff --git a/drizzle/migrations/0015_tense_patch.sql b/drizzle/migrations/0015_tense_patch.sql new file mode 100644 index 0000000..6ef3a3c --- /dev/null +++ b/drizzle/migrations/0015_tense_patch.sql @@ -0,0 +1,15 @@ +CREATE TABLE "notifications" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "ticket_id" integer, + "type" text NOT NULL, + "title" text NOT NULL, + "body" text, + "read" boolean DEFAULT false NOT NULL, + "created_at" timestamp with time zone DEFAULT now() +); +--> statement-breakpoint +ALTER TABLE "notifications" ADD CONSTRAINT "notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "notifications" ADD CONSTRAINT "notifications_ticket_id_tickets_id_fk" FOREIGN KEY ("ticket_id") REFERENCES "public"."tickets"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "notifications_user_id_idx" ON "notifications" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "notifications_user_read_idx" ON "notifications" USING btree ("user_id","read"); \ No newline at end of file diff --git a/drizzle/migrations/0016_famous_maximus.sql b/drizzle/migrations/0016_famous_maximus.sql new file mode 100644 index 0000000..3161801 --- /dev/null +++ b/drizzle/migrations/0016_famous_maximus.sql @@ -0,0 +1,12 @@ +CREATE TABLE "api_tokens" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "name" text NOT NULL, + "token_hash" text NOT NULL, + "last_used_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now(), + CONSTRAINT "api_tokens_token_hash_unique" UNIQUE("token_hash") +); +--> statement-breakpoint +ALTER TABLE "api_tokens" ADD CONSTRAINT "api_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "api_tokens_user_id_idx" ON "api_tokens" USING btree ("user_id"); \ No newline at end of file diff --git a/drizzle/migrations/0017_redundant_the_renegades.sql b/drizzle/migrations/0017_redundant_the_renegades.sql new file mode 100644 index 0000000..2b407f3 --- /dev/null +++ b/drizzle/migrations/0017_redundant_the_renegades.sql @@ -0,0 +1 @@ +ALTER TABLE "scrips" ADD COLUMN "applicable_trans_types" text; \ No newline at end of file diff --git a/drizzle/migrations/meta/0008_snapshot.json b/drizzle/migrations/meta/0008_snapshot.json new file mode 100644 index 0000000..3c5dbb0 --- /dev/null +++ b/drizzle/migrations/meta/0008_snapshot.json @@ -0,0 +1,1418 @@ +{ + "id": "6af17602-5cf9-4b59-8bb3-336c7c754f1d", + "prevId": "4b7c344a-bcb0-48a4-8950-2ebe688dac15", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0009_snapshot.json b/drizzle/migrations/meta/0009_snapshot.json new file mode 100644 index 0000000..7ca2934 --- /dev/null +++ b/drizzle/migrations/meta/0009_snapshot.json @@ -0,0 +1,1418 @@ +{ + "id": "24c1d1a9-271c-4096-b5c3-40a11dee5923", + "prevId": "6af17602-5cf9-4b59-8bb3-336c7c754f1d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0010_snapshot.json b/drizzle/migrations/meta/0010_snapshot.json new file mode 100644 index 0000000..fe3095d --- /dev/null +++ b/drizzle/migrations/meta/0010_snapshot.json @@ -0,0 +1,1550 @@ +{ + "id": "cc6c8e10-d4d3-4834-8231-c7f3d540bca9", + "prevId": "24c1d1a9-271c-4096-b5c3-40a11dee5923", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0011_snapshot.json b/drizzle/migrations/meta/0011_snapshot.json new file mode 100644 index 0000000..2d81c0d --- /dev/null +++ b/drizzle/migrations/meta/0011_snapshot.json @@ -0,0 +1,1563 @@ +{ + "id": "5cb06f19-d02d-4d40-b1d7-0842c199da77", + "prevId": "cc6c8e10-d4d3-4834-8231-c7f3d540bca9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0012_snapshot.json b/drizzle/migrations/meta/0012_snapshot.json new file mode 100644 index 0000000..76aed56 --- /dev/null +++ b/drizzle/migrations/meta/0012_snapshot.json @@ -0,0 +1,1669 @@ +{ + "id": "ad7ea082-cf92-4f13-8150-9703bc1ee7d7", + "prevId": "5cb06f19-d02d-4d40-b1d7-0842c199da77", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_permissions": { + "name": "queue_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "queue_permissions_queue_id_idx": { + "name": "queue_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "queue_permissions_team_id_idx": { + "name": "queue_permissions_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "queue_permissions_queue_id_queues_id_fk": { + "name": "queue_permissions_queue_id_queues_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "queue_permissions_team_id_teams_id_fk": { + "name": "queue_permissions_team_id_teams_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_permissions_queue_team_right_unique": { + "name": "queue_permissions_queue_team_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "team_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0013_snapshot.json b/drizzle/migrations/meta/0013_snapshot.json new file mode 100644 index 0000000..0416ec0 --- /dev/null +++ b/drizzle/migrations/meta/0013_snapshot.json @@ -0,0 +1,1775 @@ +{ + "id": "b341f290-3cdf-42d4-92e9-92abbf7bbc80", + "prevId": "ad7ea082-cf92-4f13-8150-9703bc1ee7d7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_permissions": { + "name": "queue_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "queue_permissions_queue_id_idx": { + "name": "queue_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "queue_permissions_team_id_idx": { + "name": "queue_permissions_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "queue_permissions_queue_id_queues_id_fk": { + "name": "queue_permissions_queue_id_queues_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "queue_permissions_team_id_teams_id_fk": { + "name": "queue_permissions_team_id_teams_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_permissions_queue_team_right_unique": { + "name": "queue_permissions_queue_team_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "team_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_permissions_queue_id_idx": { + "name": "user_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_permissions_user_id_idx": { + "name": "user_permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_permissions_queue_id_queues_id_fk": { + "name": "user_permissions_queue_id_queues_id_fk", + "tableFrom": "user_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_permissions_queue_user_right_unique": { + "name": "user_permissions_queue_user_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "user_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0014_snapshot.json b/drizzle/migrations/meta/0014_snapshot.json new file mode 100644 index 0000000..d885deb --- /dev/null +++ b/drizzle/migrations/meta/0014_snapshot.json @@ -0,0 +1,1782 @@ +{ + "id": "d164e7f3-7dd0-4552-8ea4-426c1cd5119d", + "prevId": "b341f290-3cdf-42d4-92e9-92abbf7bbc80", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_permissions": { + "name": "queue_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "queue_permissions_queue_id_idx": { + "name": "queue_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "queue_permissions_team_id_idx": { + "name": "queue_permissions_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "queue_permissions_queue_id_queues_id_fk": { + "name": "queue_permissions_queue_id_queues_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "queue_permissions_team_id_teams_id_fk": { + "name": "queue_permissions_team_id_teams_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_permissions_queue_team_right_unique": { + "name": "queue_permissions_queue_team_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "team_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "time_worked_minutes": { + "name": "time_worked_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_permissions_queue_id_idx": { + "name": "user_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_permissions_user_id_idx": { + "name": "user_permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_permissions_queue_id_queues_id_fk": { + "name": "user_permissions_queue_id_queues_id_fk", + "tableFrom": "user_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_permissions_queue_user_right_unique": { + "name": "user_permissions_queue_user_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "user_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0015_snapshot.json b/drizzle/migrations/meta/0015_snapshot.json new file mode 100644 index 0000000..529ee56 --- /dev/null +++ b/drizzle/migrations/meta/0015_snapshot.json @@ -0,0 +1,1910 @@ +{ + "id": "e9b58a87-8767-41ce-95b5-3aba5ad6800b", + "prevId": "d164e7f3-7dd0-4552-8ea4-426c1cd5119d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "notifications_user_id_idx": { + "name": "notifications_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_ticket_id_tickets_id_fk": { + "name": "notifications_ticket_id_tickets_id_fk", + "tableFrom": "notifications", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_permissions": { + "name": "queue_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "queue_permissions_queue_id_idx": { + "name": "queue_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "queue_permissions_team_id_idx": { + "name": "queue_permissions_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "queue_permissions_queue_id_queues_id_fk": { + "name": "queue_permissions_queue_id_queues_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "queue_permissions_team_id_teams_id_fk": { + "name": "queue_permissions_team_id_teams_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_permissions_queue_team_right_unique": { + "name": "queue_permissions_queue_team_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "team_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "time_worked_minutes": { + "name": "time_worked_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_permissions_queue_id_idx": { + "name": "user_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_permissions_user_id_idx": { + "name": "user_permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_permissions_queue_id_queues_id_fk": { + "name": "user_permissions_queue_id_queues_id_fk", + "tableFrom": "user_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_permissions_queue_user_right_unique": { + "name": "user_permissions_queue_user_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "user_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0016_snapshot.json b/drizzle/migrations/meta/0016_snapshot.json new file mode 100644 index 0000000..7ca2ff9 --- /dev/null +++ b/drizzle/migrations/meta/0016_snapshot.json @@ -0,0 +1,1999 @@ +{ + "id": "ad535ff0-077f-4bf5-9cd6-c8cedfa6b4ab", + "prevId": "e9b58a87-8767-41ce-95b5-3aba5ad6800b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_tokens": { + "name": "api_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "api_tokens_user_id_idx": { + "name": "api_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_tokens_user_id_users_id_fk": { + "name": "api_tokens_user_id_users_id_fk", + "tableFrom": "api_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_tokens_token_hash_unique": { + "name": "api_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "notifications_user_id_idx": { + "name": "notifications_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_ticket_id_tickets_id_fk": { + "name": "notifications_ticket_id_tickets_id_fk", + "tableFrom": "notifications", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_permissions": { + "name": "queue_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "queue_permissions_queue_id_idx": { + "name": "queue_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "queue_permissions_team_id_idx": { + "name": "queue_permissions_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "queue_permissions_queue_id_queues_id_fk": { + "name": "queue_permissions_queue_id_queues_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "queue_permissions_team_id_teams_id_fk": { + "name": "queue_permissions_team_id_teams_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_permissions_queue_team_right_unique": { + "name": "queue_permissions_queue_team_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "team_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "time_worked_minutes": { + "name": "time_worked_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_permissions_queue_id_idx": { + "name": "user_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_permissions_user_id_idx": { + "name": "user_permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_permissions_queue_id_queues_id_fk": { + "name": "user_permissions_queue_id_queues_id_fk", + "tableFrom": "user_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_permissions_queue_user_right_unique": { + "name": "user_permissions_queue_user_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "user_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/0017_snapshot.json b/drizzle/migrations/meta/0017_snapshot.json new file mode 100644 index 0000000..0771d12 --- /dev/null +++ b/drizzle/migrations/meta/0017_snapshot.json @@ -0,0 +1,2005 @@ +{ + "id": "8cede180-5920-4578-ac62-e1e03bf59471", + "prevId": "ad535ff0-077f-4bf5-9cd6-c8cedfa6b4ab", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_tokens": { + "name": "api_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "api_tokens_user_id_idx": { + "name": "api_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_tokens_user_id_users_id_fk": { + "name": "api_tokens_user_id_users_id_fk", + "tableFrom": "api_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_tokens_token_hash_unique": { + "name": "api_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "custom_field_values_ticket_id_idx": { + "name": "custom_field_values_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_field_values_custom_field_id_idx": { + "name": "custom_field_values_custom_field_id_idx", + "columns": [ + { + "expression": "custom_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_custom_field_id_custom_fields_id_fk": { + "name": "custom_field_values_custom_field_id_custom_fields_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_field_values_ticket_id_tickets_id_fk": { + "name": "custom_field_values_ticket_id_tickets_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_field_values_cf_id_ticket_id_value_unique": { + "name": "custom_field_values_cf_id_ticket_id_value_unique", + "nullsNotDistinct": false, + "columns": [ + "custom_field_id", + "ticket_id", + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_fields": { + "name": "custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "values": { + "name": "values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_values": { + "name": "max_values", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custom_fields_key_unique": { + "name": "custom_fields_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_widgets": { + "name": "dashboard_widgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_id": { + "name": "view_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widget_type": { + "name": "widget_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"x\":0,\"y\":0,\"w\":4,\"h\":2}'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widgets_dashboard_id_dashboards_id_fk": { + "name": "dashboard_widgets_dashboard_id_dashboards_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_widgets_view_id_views_id_fk": { + "name": "dashboard_widgets_view_id_views_id_fk", + "tableFrom": "dashboard_widgets", + "tableTo": "views", + "columnsFrom": [ + "view_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_team_id_teams_id_fk": { + "name": "dashboards_team_id_teams_id_fk", + "tableFrom": "dashboards", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lifecycles": { + "name": "lifecycles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lifecycles_name_unique": { + "name": "lifecycles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "notifications_user_id_idx": { + "name": "notifications_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_ticket_id_tickets_id_fk": { + "name": "notifications_ticket_id_tickets_id_fk", + "tableFrom": "notifications", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_custom_fields": { + "name": "queue_custom_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custom_field_id": { + "name": "custom_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "queue_custom_fields_queue_id_queues_id_fk": { + "name": "queue_custom_fields_queue_id_queues_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queue_custom_fields_custom_field_id_custom_fields_id_fk": { + "name": "queue_custom_fields_custom_field_id_custom_fields_id_fk", + "tableFrom": "queue_custom_fields", + "tableTo": "custom_fields", + "columnsFrom": [ + "custom_field_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_custom_fields_queue_id_custom_field_id_unique": { + "name": "queue_custom_fields_queue_id_custom_field_id_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "custom_field_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queue_permissions": { + "name": "queue_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "queue_permissions_queue_id_idx": { + "name": "queue_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "queue_permissions_team_id_idx": { + "name": "queue_permissions_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "queue_permissions_queue_id_queues_id_fk": { + "name": "queue_permissions_queue_id_queues_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "queue_permissions_team_id_teams_id_fk": { + "name": "queue_permissions_team_id_teams_id_fk", + "tableFrom": "queue_permissions", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queue_permissions_queue_team_right_unique": { + "name": "queue_permissions_queue_team_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "team_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.queues": { + "name": "queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_id": { + "name": "lifecycle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "queues_lifecycle_id_lifecycles_id_fk": { + "name": "queues_lifecycle_id_lifecycles_id_fk", + "tableFrom": "queues", + "tableTo": "lifecycles", + "columnsFrom": [ + "lifecycle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "queues_team_id_teams_id_fk": { + "name": "queues_team_id_teams_id_fk", + "tableFrom": "queues", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "queues_name_unique": { + "name": "queues_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scrips": { + "name": "scrips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_config": { + "name": "condition_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_config": { + "name": "action_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TransactionCreate'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "applicable_trans_types": { + "name": "applicable_trans_types", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "scrips_queue_id_idx": { + "name": "scrips_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scrips_queue_id_queues_id_fk": { + "name": "scrips_queue_id_queues_id_fk", + "tableFrom": "scrips", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "scrips_template_id_templates_id_fk": { + "name": "scrips_template_id_templates_id_fk", + "tableFrom": "scrips", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_members_team_id_user_id_unique": { + "name": "team_members_team_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_unique": { + "name": "teams_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "templates_queue_id_queues_id_fk": { + "name": "templates_queue_id_queues_id_fk", + "tableFrom": "templates", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ticket_links": { + "name": "ticket_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_ticket_id": { + "name": "target_ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "link_type": { + "name": "link_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "ticket_links_ticket_id_idx": { + "name": "ticket_links_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ticket_links_target_ticket_id_idx": { + "name": "ticket_links_target_ticket_id_idx", + "columns": [ + { + "expression": "target_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ticket_links_ticket_id_tickets_id_fk": { + "name": "ticket_links_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_target_ticket_id_tickets_id_fk": { + "name": "ticket_links_target_ticket_id_tickets_id_fk", + "tableFrom": "ticket_links", + "tableTo": "tickets", + "columnsFrom": [ + "target_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ticket_links_creator_id_users_id_fk": { + "name": "ticket_links_creator_id_users_id_fk", + "tableFrom": "ticket_links", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ticket_links_ticket_target_type_unique": { + "name": "ticket_links_ticket_target_type_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_id", + "target_ticket_id", + "link_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tickets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tickets_queue_id_idx": { + "name": "tickets_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tickets_queue_id_queues_id_fk": { + "name": "tickets_queue_id_queues_id_fk", + "tableFrom": "tickets", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_owner_id_users_id_fk": { + "name": "tickets_owner_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tickets_team_id_teams_id_fk": { + "name": "tickets_team_id_teams_id_fk", + "tableFrom": "tickets", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tickets_creator_id_users_id_fk": { + "name": "tickets_creator_id_users_id_fk", + "tableFrom": "tickets", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_attachments": { + "name": "transaction_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "transaction_id": { + "name": "transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transaction_attachments_tx_id_idx": { + "name": "transaction_attachments_tx_id_idx", + "columns": [ + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_attachments_transaction_id_transactions_id_fk": { + "name": "transaction_attachments_transaction_id_transactions_id_fk", + "tableFrom": "transaction_attachments", + "tableTo": "transactions", + "columnsFrom": [ + "transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_id": { + "name": "ticket_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field": { + "name": "field", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "time_worked_minutes": { + "name": "time_worked_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "transactions_ticket_id_idx": { + "name": "transactions_ticket_id_idx", + "columns": [ + { + "expression": "ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactions_created_at_idx": { + "name": "transactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactions_ticket_id_tickets_id_fk": { + "name": "transactions_ticket_id_tickets_id_fk", + "tableFrom": "transactions", + "tableTo": "tickets", + "columnsFrom": [ + "ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transactions_creator_id_users_id_fk": { + "name": "transactions_creator_id_users_id_fk", + "tableFrom": "transactions", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "right_name": { + "name": "right_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_permissions_queue_id_idx": { + "name": "user_permissions_queue_id_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_permissions_user_id_idx": { + "name": "user_permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_permissions_queue_id_queues_id_fk": { + "name": "user_permissions_queue_id_queues_id_fk", + "tableFrom": "user_permissions", + "tableTo": "queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_permissions_queue_user_right_unique": { + "name": "user_permissions_queue_user_right_unique", + "nullsNotDistinct": false, + "columns": [ + "queue_id", + "user_id", + "right_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staff'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "sort_key": { + "name": "sort_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'updated'" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "views_creator_id_users_id_fk": { + "name": "views_creator_id_users_id_fk", + "tableFrom": "views", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index f217a4b..95295b0 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -57,6 +57,76 @@ "when": 1781009018666, "tag": "0007_flimsy_roughhouse", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1781039674211, + "tag": "0008_sturdy_prism", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1781039770418, + "tag": "0009_tiny_lady_vermin", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1781040536590, + "tag": "0010_misty_morg", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1781042321413, + "tag": "0011_breezy_tyrannus", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1781043175153, + "tag": "0012_living_photon", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1781043729230, + "tag": "0013_bored_silvermane", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1781045611610, + "tag": "0014_cloudy_siren", + "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1781078349499, + "tag": "0015_tense_patch", + "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1781078511943, + "tag": "0016_famous_maximus", + "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1781095552496, + "tag": "0017_redundant_the_renegades", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 1f96fc5..a0b3098 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@types/nodemailer": "^8.0.0", + "jose": "^6.2.3", "nodemailer": "^8.0.10" } } diff --git a/scripts/seed-users.ts b/scripts/seed-users.ts new file mode 100644 index 0000000..6e61e62 --- /dev/null +++ b/scripts/seed-users.ts @@ -0,0 +1,28 @@ +import { drizzle } from 'drizzle-orm/node-postgres'; +import { Pool } from 'pg'; +import { users } from '../src/db/schema.ts'; + +const pool = new Pool({ connectionString: process.env.DATABASE_URL! }); +const db = drizzle(pool); + +const BATCH = 100; +const TOTAL = 1000; +const password = await Bun.password.hash('password'); + +console.log(`Inserting ${TOTAL} users...`); +for (let i = 0; i < TOTAL; i += BATCH) { + const batch = []; + for (let j = i; j < Math.min(i + BATCH, TOTAL); j++) { + const n = String(j).padStart(4, '0'); + batch.push({ + username: `user${n}`, + email: `user${n}@test.local`, + role: 'staff', + password_hash: password, + }); + } + await db.insert(users).values(batch as any).onConflictDoNothing(); + process.stdout.write('.'); +} +console.log(`\nDone. ${TOTAL} users seeded.`); +await pool.end(); diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts new file mode 100644 index 0000000..8c279e3 --- /dev/null +++ b/src/auth/middleware.ts @@ -0,0 +1,144 @@ +import type { Context, Next } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import * as jose from 'jose'; +import { config } from '../config.ts'; +import type { Db } from '../db/index.ts'; +import { users, apiTokens } from '../db/schema.ts'; +import { eq } from 'drizzle-orm'; + +export interface AuthUser { + userId: string; + username: string; + role: string; +} + +declare module 'hono' { + interface ContextVariableMap { + user: AuthUser; + } +} + +const secret = new TextEncoder().encode(config.JWT_SECRET); + +export async function createToken(user: { id: string; username: string; role: string }): Promise { + return await new jose.SignJWT({ username: user.username, role: user.role }) + .setProtectedHeader({ alg: 'HS256' }) + .setSubject(user.id) + .setIssuedAt() + .setExpirationTime('7d') + .sign(secret); +} + +async function verifyJwt(token: string): Promise { + try { + const { payload } = await jose.jwtVerify(token, secret); + return { + userId: payload.sub!, + username: payload.username as string, + role: payload.role as string, + }; + } catch { + return null; + } +} + +async function verifyApiToken(db: Db, token: string): Promise { + try { + // Find all tokens and verify against hash + const allTokens = await db.query.apiTokens.findMany(); + for (const t of allTokens) { + const valid = await Bun.password.verify(token, t.token_hash); + if (valid) { + // Update last_used_at + await db.update(apiTokens) + .set({ last_used_at: new Date() } as any) + .where(eq(apiTokens.id, t.id)); + + const user = await db.query.users.findFirst({ + where: eq(users.id, t.user_id), + }); + if (user) { + return { + userId: user.id, + username: user.username, + role: user.role, + }; + } + } + } + return null; + } catch { + return null; + } +} + +function extractToken(c: Context): string | null { + const auth = c.req.header('Authorization'); + if (auth?.startsWith('Bearer ')) { + return auth.slice(7); + } + + const cookie = c.req.header('Cookie'); + if (cookie) { + const match = cookie.match(/(?:^|;\s*)token=([^;]*)/); + if (match?.[1]) return match[1]; + } + + return null; +} + +export function createAuthMiddleware(db: Db) { + async function verifyToken(token: string): Promise { + if (token.startsWith('tessera_')) { + return await verifyApiToken(db, token); + } + return await verifyJwt(token); + } + + async function requireAuth(c: Context, next: Next) { + const token = extractToken(c); + if (!token) { + throw new HTTPException(401, { message: 'Authentication required' }); + } + const user = await verifyToken(token); + if (!user) { + throw new HTTPException(401, { message: 'Invalid or expired token' }); + } + c.set('user', user); + await next(); + } + + async function requireAdmin(c: Context, next: Next) { + const token = extractToken(c); + if (!token) { + throw new HTTPException(401, { message: 'Authentication required' }); + } + const user = await verifyToken(token); + if (!user) { + throw new HTTPException(401, { message: 'Invalid or expired token' }); + } + if (user.role !== 'admin') { + throw new HTTPException(403, { message: 'Admin access required' }); + } + c.set('user', user); + await next(); + } + + async function optionalAuth(c: Context, next: Next) { + const token = extractToken(c); + if (token) { + const user = await verifyToken(token); + if (user) { + c.set('user', user); + } + } + await next(); + } + + return { requireAuth, requireAdmin, optionalAuth }; +} + +export function getUserId(c: Context): string { + const user = c.get('user'); + return user?.userId ?? '00000000-0000-0000-0000-000000000000'; +} diff --git a/src/auth/permissions.ts b/src/auth/permissions.ts new file mode 100644 index 0000000..be7bb84 --- /dev/null +++ b/src/auth/permissions.ts @@ -0,0 +1,86 @@ +import { HTTPException } from 'hono/http-exception'; +import type { Context } from 'hono'; +import type { Db } from '../db/index.ts'; +import { teamMembers, queuePermissions, userPermissions } from '../db/schema.ts'; +import { and, eq, inArray } from 'drizzle-orm'; +import type { AuthUser } from './middleware.ts'; + +export type TicketRight = 'ticket.view' | 'ticket.create' | 'ticket.reply' | 'ticket.comment' | 'ticket.modify' | 'queue.admin'; + +const RIGHT_HIERARCHY: Record = { + 'queue.admin': ['ticket.view', 'ticket.create', 'ticket.reply', 'ticket.comment', 'ticket.modify', 'queue.admin'], + 'ticket.modify': ['ticket.view', 'ticket.reply', 'ticket.comment', 'ticket.modify'], + 'ticket.reply': ['ticket.view', 'ticket.reply'], + 'ticket.comment': ['ticket.view', 'ticket.comment'], + 'ticket.create': ['ticket.create'], + 'ticket.view': ['ticket.view'], +}; + +/** + * Check whether a user has a specific right on a queue. + * Admins bypass all permission checks. + * Rights come from two sources: team memberships and per-user grants. + * Higher rights imply lower rights (e.g., queue.admin implies ticket.view). + */ +export async function userHasRight( + db: Db, + user: AuthUser, + queueId: string, + right: TicketRight, +): Promise { + // Admins have all rights + if (user.role === 'admin') return true; + + const neededRights = RIGHT_HIERARCHY[right] ?? [right]; + + // Check per-user permissions first (direct grant) + const userPerm = await db.query.userPermissions.findFirst({ + where: (table, { and, eq: eqFn, inArray: inArr }) => + and( + eqFn(table.user_id, user.userId), + eqFn(table.queue_id, queueId), + inArr(table.right_name, neededRights), + ), + }); + + if (userPerm) return true; + + // Check team permissions (inherited) + const memberships = await db.query.teamMembers.findMany({ + where: eq(teamMembers.user_id, user.userId), + }); + + const teamIds = memberships.map((m) => m.team_id); + if (teamIds.length === 0) return false; + + const teamPerm = await db.query.queuePermissions.findFirst({ + where: (table, { and, eq: eqFn, inArray: inArr }) => + and( + inArr(table.team_id, teamIds), + eqFn(table.queue_id, queueId), + inArr(table.right_name, neededRights), + ), + }); + + return teamPerm !== undefined; +} + +/** + * Require a specific right on a queue. Throws 403 if the user lacks the right. + */ +export async function requireRight( + c: Context, + db: Db, + queueId: string, + right: TicketRight, +): Promise { + const user = c.get('user'); + if (!user) { + throw new HTTPException(401, { message: 'Authentication required' }); + } + + const has = await userHasRight(db, user, queueId, right); + if (!has) { + throw new HTTPException(403, { message: `Missing required right: ${right}` }); + } +} diff --git a/src/config.ts b/src/config.ts index aa59af0..7eef0a6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,6 +9,8 @@ const configSchema = z.object({ SMTP_USER: z.string().optional(), SMTP_PASS: z.string().optional(), SMTP_FROM: z.string().default('tessera@localhost'), + UPLOAD_DIR: z.string().default('./data/uploads'), + JWT_SECRET: z.string().default('tessera-dev-secret-change-in-production'), }); export const config = configSchema.parse(process.env); diff --git a/src/db/schema.ts b/src/db/schema.ts index 3147239..ad67253 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -5,6 +5,8 @@ export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), username: text('username').notNull().unique(), email: text('email'), + password_hash: text('password_hash'), + role: text('role').notNull().default('staff'), created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), }); @@ -49,6 +51,7 @@ export const transactions = pgTable('transactions', { old_value: text('old_value'), new_value: text('new_value'), data: jsonb('data'), + time_worked_minutes: integer('time_worked_minutes').default(0), creator_id: uuid('creator_id').notNull().references(() => users.id), created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), }, (table) => ({ @@ -78,6 +81,7 @@ export const scrips = pgTable('scrips', { stage: text('stage').notNull().default('TransactionCreate'), sort_order: integer('sort_order').notNull().default(0), disabled: boolean('disabled').notNull().default(false), + applicable_trans_types: text('applicable_trans_types'), created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), }, (table) => ({ queueIdIdx: index('scrips_queue_id_idx').on(table.queue_id), @@ -160,6 +164,78 @@ export const dashboards = pgTable('dashboards', { created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), }); +export const transactionAttachments = pgTable('transaction_attachments', { + id: uuid('id').primaryKey().defaultRandom(), + transaction_id: uuid('transaction_id').references(() => transactions.id, { onDelete: 'cascade' }), + filename: text('filename').notNull(), + mime_type: text('mime_type').notNull().default('application/octet-stream'), + size_bytes: integer('size_bytes').notNull().default(0), + storage_path: text('storage_path').notNull(), + created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), +}, (table) => ({ + transactionIdIdx: index('transaction_attachments_tx_id_idx').on(table.transaction_id), +})); + +export const queuePermissions = pgTable('queue_permissions', { + id: uuid('id').primaryKey().defaultRandom(), + queue_id: uuid('queue_id').notNull().references(() => queues.id, { onDelete: 'cascade' }), + team_id: uuid('team_id').notNull().references(() => teams.id, { onDelete: 'cascade' }), + right_name: text('right_name').notNull(), +}, (table) => ({ + uniqueRight: unique('queue_permissions_queue_team_right_unique').on(table.queue_id, table.team_id, table.right_name), + queueIdIdx: index('queue_permissions_queue_id_idx').on(table.queue_id), + teamIdIdx: index('queue_permissions_team_id_idx').on(table.team_id), +})); + +export const apiTokens = pgTable('api_tokens', { + id: uuid('id').primaryKey().defaultRandom(), + user_id: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + name: text('name').notNull(), + token_hash: text('token_hash').notNull().unique(), + last_used_at: timestamp('last_used_at', { withTimezone: true }), + created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), +}, (table) => ({ + userIdIdx: index('api_tokens_user_id_idx').on(table.user_id), +})); + +export const notifications = pgTable('notifications', { + id: uuid('id').primaryKey().defaultRandom(), + user_id: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + ticket_id: integer('ticket_id').references(() => tickets.id, { onDelete: 'cascade' }), + type: text('type').notNull(), // 'assigned', 'mentioned', 'commented', 'scrip_fired' + title: text('title').notNull(), + body: text('body'), + read: boolean('read').notNull().default(false), + created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), +}, (table) => ({ + userIdIdx: index('notifications_user_id_idx').on(table.user_id), + unreadIdx: index('notifications_user_read_idx').on(table.user_id, table.read), +})); + +export const userPermissions = pgTable('user_permissions', { + id: uuid('id').primaryKey().defaultRandom(), + queue_id: uuid('queue_id').notNull().references(() => queues.id, { onDelete: 'cascade' }), + user_id: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + right_name: text('right_name').notNull(), +}, (table) => ({ + uniqueRight: unique('user_permissions_queue_user_right_unique').on(table.queue_id, table.user_id, table.right_name), + queueIdIdx: index('user_permissions_queue_id_idx').on(table.queue_id), + userIdIdx: index('user_permissions_user_id_idx').on(table.user_id), +})); + +export const ticketLinks = pgTable('ticket_links', { + id: uuid('id').primaryKey().defaultRandom(), + ticket_id: integer('ticket_id').notNull().references(() => tickets.id, { onDelete: 'cascade' }), + target_ticket_id: integer('target_ticket_id').notNull().references(() => tickets.id, { onDelete: 'cascade' }), + link_type: text('link_type').notNull(), + creator_id: uuid('creator_id').notNull().references(() => users.id), + created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), +}, (table) => ({ + uniqueLink: unique('ticket_links_ticket_target_type_unique').on(table.ticket_id, table.target_ticket_id, table.link_type), + ticketIdIdx: index('ticket_links_ticket_id_idx').on(table.ticket_id), + targetTicketIdIdx: index('ticket_links_target_ticket_id_idx').on(table.target_ticket_id), +})); + export const dashboardWidgets = pgTable('dashboard_widgets', { id: uuid('id').primaryKey().defaultRandom(), dashboard_id: uuid('dashboard_id').notNull().references(() => dashboards.id, { onDelete: 'cascade' }), diff --git a/src/db/seed.ts b/src/db/seed.ts index fe5571f..bf4c02f 100644 --- a/src/db/seed.ts +++ b/src/db/seed.ts @@ -7,8 +7,11 @@ import { customFieldValues, lifecycles, queueCustomFields, + queuePermissions, queues, scrips, + teamMembers, + teams, templates, tickets, transactions, @@ -52,7 +55,7 @@ function createSeedDb(pool: Pool) { } type Db = ReturnType; -type UserSeed = { id: string; username: string; email: string }; +type UserSeed = { id: string; username: string; email: string; role?: string; password_hash?: string }; type QueueSeed = { name: string; description: string }; type FieldSeed = { key?: string; @@ -73,12 +76,19 @@ function makeFieldKey(value: string): string { } async function ensureUser(db: Db, seed: UserSeed): Promise { + const setData = { + username: seed.username, + email: seed.email, + role: seed.role ?? 'staff', + password_hash: seed.password_hash ?? null, + }; + const existingById = await db.query.users.findFirst({ where: eq(users.id, seed.id), }); if (existingById) { await db.update(users) - .set({ username: seed.username, email: seed.email }) + .set(setData) .where(eq(users.id, seed.id)); return existingById.id; } @@ -88,12 +98,12 @@ async function ensureUser(db: Db, seed: UserSeed): Promise { }); if (existingByUsername) { await db.update(users) - .set({ email: seed.email }) + .set(setData) .where(eq(users.id, existingByUsername.id)); return existingByUsername.id; } - const [created] = await db.insert(users).values(seed).returning(); + const [created] = await db.insert(users).values({ ...seed, ...setData }).returning(); if (!created) throw new Error(`Failed to seed user ${seed.username}`); return created.id; } @@ -315,6 +325,7 @@ async function ensureTicket( async function resetDatabase(db: Db) { await db.delete(customFieldValues); + await db.delete(queuePermissions); await db.delete(transactions); await db.delete(queueCustomFields); await db.delete(dashboardWidgets); @@ -346,34 +357,68 @@ async function main() { await resetDatabase(db); } + const userPassword = await Bun.password.hash('password'); + const adminPassword = await Bun.password.hash('admin'); + const userIds = { system: await ensureUser(db, { id: SYSTEM_USER_ID, username: 'system', email: 'system@tessera.local', }), + admin: await ensureUser(db, { + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + username: 'admin', + email: 'admin@tessera.local', + role: 'admin', + password_hash: adminPassword, + }), dispatcher: await ensureUser(db, { id: '11111111-1111-4111-8111-111111111111', username: 'maria.dispatch', email: 'maria.dispatch@tessera.local', + password_hash: userPassword, }), technician: await ensureUser(db, { id: '22222222-2222-4222-8222-222222222222', username: 'liam.field', email: 'liam.field@tessera.local', + password_hash: userPassword, }), facilities: await ensureUser(db, { id: '33333333-3333-4333-8333-333333333333', username: 'nora.facilities', email: 'nora.facilities@tessera.local', + password_hash: userPassword, }), security: await ensureUser(db, { id: '44444444-4444-4444-8444-444444444444', username: 'sam.security', email: 'sam.security@tessera.local', + password_hash: userPassword, }), }; + // Create demo team and assign all staff users to it + const [supportTeam] = await db.insert(teams).values({ + name: 'Support team', + description: 'Demo support team with full queue access', + }).onConflictDoUpdate({ + target: teams.name, + set: { description: 'Demo support team with full queue access' }, + }).returning(); + + if (supportTeam) { + // Add all staff users to the team + const staffIds = [userIds.dispatcher, userIds.technician, userIds.facilities, userIds.security]; + for (const userId of staffIds) { + await db.insert(teamMembers).values({ + team_id: supportTeam.id, + user_id: userId, + }).onConflictDoNothing(); + } + } + const lifecycle = await ensureLifecycle(db); const supportQueue = await ensureQueue(db, lifecycle.id, { @@ -432,6 +477,20 @@ async function main() { await attachFieldToQueue(db, fieldQueue.id, assetField.id, 40); await attachFieldToQueue(db, supportQueue.id, outcomeField.id, 50); + // Grant the support team full access to all demo queues + if (supportTeam) { + const allRights = ['ticket.view', 'ticket.create', 'ticket.reply', 'ticket.comment', 'ticket.modify']; + for (const queue of [supportQueue, fieldQueue, facilitiesQueue, securityQueue]) { + for (const right of allRights) { + await db.insert(queuePermissions).values({ + queue_id: queue.id, + team_id: supportTeam.id, + right_name: right, + }).onConflictDoNothing(); + } + } + } + const resolveTemplate = await ensureTemplate( db, 'Demo resolution note', diff --git a/src/index.ts b/src/index.ts index 18ff469..0c3444f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { createDb } from './db/index.ts'; import type { Db } from './db/index.ts'; import { errorHandler } from './middleware/error.ts'; import { requestLogger } from './middleware/logging.ts'; +import { createAuthMiddleware } from './auth/middleware.ts'; import healthRouter from './routes/health.ts'; import { createTicketsRouter } from './routes/tickets.ts'; import { createQueuesRouter } from './routes/queues.ts'; @@ -15,6 +16,11 @@ import { createTemplatesRouter } from './routes/templates.ts'; import { createViewsRouter } from './routes/views.ts'; import { createDashboardsRouter } from './routes/dashboards.ts'; import { createTeamsRouter } from './routes/teams.ts'; +import { createAttachmentsRouter } from './routes/attachments.ts'; +import { createAuthRouter } from './routes/auth.ts'; +import { createQueuePermissionsRouter } from './routes/queue-permissions.ts'; +import { createNotificationsRouter } from './routes/notifications.ts'; +import { startScheduler } from './scrip/scheduler.ts'; let db: Db | null = null; @@ -30,17 +36,39 @@ const app = new Hono(); app.use('*', requestLogger); app.onError(errorHandler); +const { requireAuth, requireAdmin } = createAuthMiddleware(getDb()); + +// Public routes app.route('/health', healthRouter); -app.route('/tickets', createTicketsRouter(getDb())); -app.route('/queues', createQueuesRouter(getDb())); -app.route('/scrips', createScripsRouter(getDb())); -app.route('/custom-fields', createCustomFieldsRouter(getDb())); -app.route('/lifecycles', createLifecyclesRouter(getDb())); -app.route('/users', createUsersRouter(getDb())); -app.route('/templates', createTemplatesRouter(getDb())); -app.route('/views', createViewsRouter(getDb())); -app.route('/dashboards', createDashboardsRouter(getDb())); -app.route('/teams', createTeamsRouter(getDb())); +app.route('/', createAuthRouter(getDb())); + +// Ticket routes โ€” require authentication +const ticketsWithAuth = new Hono(); +ticketsWithAuth.use('*', requireAuth); +ticketsWithAuth.route('/tickets', createTicketsRouter(getDb())); +ticketsWithAuth.route('/', createNotificationsRouter(getDb())); +app.route('/', ticketsWithAuth); + +// Attachment serving โ€” require authentication +const attachmentsWithAuth = new Hono(); +attachmentsWithAuth.use('*', requireAuth); +attachmentsWithAuth.route('/', createAttachmentsRouter(getDb())); +app.route('/', attachmentsWithAuth); + +// Admin routes โ€” require admin role +const admin = new Hono(); +admin.use('*', requireAdmin); +admin.route('/queues', createQueuesRouter(getDb())); +admin.route('/scrips', createScripsRouter(getDb())); +admin.route('/custom-fields', createCustomFieldsRouter(getDb())); +admin.route('/lifecycles', createLifecyclesRouter(getDb())); +admin.route('/users', createUsersRouter(getDb())); +admin.route('/templates', createTemplatesRouter(getDb())); +admin.route('/views', createViewsRouter(getDb())); +admin.route('/dashboards', createDashboardsRouter(getDb())); +admin.route('/teams', createTeamsRouter(getDb())); +admin.route('/', createQueuePermissionsRouter(getDb())); +app.route('/', admin); export default app; export { app }; @@ -54,4 +82,7 @@ if (Bun.main === import.meta.path) { development: false, }); console.log(`Server running at http://${config.SERVER_HOST}:${config.SERVER_PORT}`); + + // Start the scrip scheduler (runs every 5 minutes) + startScheduler(getDb()); } diff --git a/src/lifecycle/validator.ts b/src/lifecycle/validator.ts index 296dbce..61bafd0 100644 --- a/src/lifecycle/validator.ts +++ b/src/lifecycle/validator.ts @@ -5,13 +5,17 @@ export interface LifecycleDefinition { inactive: string[]; }; transitions: Record; + transition_rights?: Record; // "fromโ†’to" โ†’ rightName } export interface ValidationResult { valid: boolean; error?: string; + requiredRight?: string; // Named right required for this transition, if any } +const FALLBACK_RIGHT = 'ticket.modify'; + export class LifecycleValidator { validateTransition( lifecycleDef: LifecycleDefinition, @@ -35,13 +39,15 @@ export class LifecycleValidator { const allowedTransitions = this.getAllowedTransitions(lifecycleDef, fromStatus); if (allowedTransitions.includes(toStatus)) { - return { valid: true }; + const right = this.getRequiredRight(lifecycleDef, fromStatus, toStatus); + return { valid: true, requiredRight: right ?? FALLBACK_RIGHT }; } // Also handle wildcard "*" -> any transition const wildcardTransitions = this.getAllowedTransitions(lifecycleDef, '*'); if (wildcardTransitions.includes(toStatus)) { - return { valid: true }; + const right = this.getRequiredRight(lifecycleDef, fromStatus, toStatus); + return { valid: true, requiredRight: right ?? FALLBACK_RIGHT }; } return { @@ -50,6 +56,37 @@ export class LifecycleValidator { }; } + /** + * Get the required right for a transition using RT's 4-level priority: + * 1. exact "fromโ†’to" + * 2. wildcard from "*โ†’to" + * 3. wildcard to "fromโ†’*" + * 4. full wildcard "*โ†’*" + * 5. fallback: ticket.modify + */ + getRequiredRight( + lifecycleDef: LifecycleDefinition, + fromStatus: string, + toStatus: string, + ): string | null { + const rights = lifecycleDef.transition_rights ?? {}; + + // Priority 1: exact match + if (rights[`${fromStatus}โ†’${toStatus}`]) return rights[`${fromStatus}โ†’${toStatus}`]; + + // Priority 2: wildcard from + if (rights[`*โ†’${toStatus}`]) return rights[`*โ†’${toStatus}`]; + + // Priority 3: wildcard to + if (rights[`${fromStatus}โ†’*`]) return rights[`${fromStatus}โ†’*`]; + + // Priority 4: full wildcard + if (rights['*โ†’*']) return rights['*โ†’*']; + + // Priority 5: fallback + return null; + } + isResolvedStatus(lifecycleDef: LifecycleDefinition, status: string): boolean { return lifecycleDef.statuses.inactive.includes(status); } @@ -58,16 +95,12 @@ export class LifecycleValidator { lifecycleDef: LifecycleDefinition, fromStatus: string, ): string[] { - // Direct transition if (lifecycleDef.transitions[fromStatus]) { return lifecycleDef.transitions[fromStatus]!; } - - // Wildcard transitions if (lifecycleDef.transitions['*']) { return lifecycleDef.transitions['*']!; } - return []; } } diff --git a/src/models/ticket.ts b/src/models/ticket.ts index 1a4c083..b63fc2a 100644 --- a/src/models/ticket.ts +++ b/src/models/ticket.ts @@ -22,4 +22,6 @@ export const CommentSchema = z.object({ body: z.string().min(1), creator_id: z.string().optional().default('00000000-0000-0000-0000-000000000000'), internal: z.boolean().optional().default(false), + attachment_ids: z.array(z.string()).optional(), + time_worked_minutes: z.number().int().min(0).optional(), }); diff --git a/src/models/transaction.ts b/src/models/transaction.ts index 59f8823..8d86670 100644 --- a/src/models/transaction.ts +++ b/src/models/transaction.ts @@ -11,6 +11,8 @@ export const TransactionType = { Comment: 'Comment', CustomField: 'CustomField', Correspond: 'Correspond', + LinkCreate: 'LinkCreate', + LinkDelete: 'LinkDelete', } as const; export type TransactionType = (typeof TransactionType)[keyof typeof TransactionType]; diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts new file mode 100644 index 0000000..44081be --- /dev/null +++ b/src/routes/attachments.ts @@ -0,0 +1,190 @@ +import { Hono } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import { existsSync, mkdirSync, createReadStream } from 'node:fs'; +import { join, extname } from 'node:path'; +import { writeFile, unlink } from 'node:fs/promises'; +import { randomUUID } from 'node:crypto'; +import type { Db } from '../db/index.ts'; +import { config } from '../config.ts'; +import { transactionAttachments, transactions, tickets } from '../db/schema.ts'; +import { eq, inArray } from 'drizzle-orm'; + +function ensureDir(dir: string) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } +} + +function storageDir(): string { + const now = new Date(); + const year = now.getFullYear().toString(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const dir = join(config.UPLOAD_DIR, year, month); + ensureDir(dir); + return dir; +} + +const MIME_MAP: Record = { + '.txt': 'text/plain', + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.json': 'application/json', + '.xml': 'application/xml', + '.pdf': 'application/pdf', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.webp': 'image/webp', + '.zip': 'application/zip', + '.gz': 'application/gzip', + '.tar': 'application/x-tar', + '.csv': 'text/csv', + '.doc': 'application/msword', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.xls': 'application/vnd.ms-excel', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.ppt': 'application/vnd.ms-powerpoint', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.mp4': 'video/mp4', + '.mp3': 'audio/mpeg', + '.wav': 'audio/wav', + '.mov': 'video/quicktime', + '.avif': 'image/avif', + '.heic': 'image/heic', + '.heif': 'image/heif', + '.log': 'text/plain', + '.md': 'text/markdown', + '.yaml': 'text/yaml', + '.yml': 'text/yaml', +}; + +function guessMimeType(filename: string): string { + const ext = extname(filename).toLowerCase(); + return MIME_MAP[ext] || 'application/octet-stream'; +} + +export function createAttachmentsRouter(db: Db): Hono { + const router = new Hono(); + + // POST /tickets/:id/attachments โ€” upload files (returns metadata, no transaction created yet) + router.post('/tickets/:id/attachments', async (c) => { + const ticketId = Number(c.req.param('id')); + + const ticket = await db.query.tickets.findFirst({ + where: eq(tickets.id, ticketId), + }); + + if (!ticket) { + throw new HTTPException(404, { message: 'Ticket not found' }); + } + + const formData = await c.req.formData(); + const files = formData.getAll('files') as File[]; + + if (files.length === 0) { + throw new HTTPException(422, { message: 'No files provided' }); + } + + const dir = storageDir(); + const result: Array<{ + id: string; + filename: string; + mime_type: string; + size_bytes: number; + }> = []; + + for (const file of files) { + if (!(file instanceof File)) continue; + + const ext = extname(file.name); + const storedName = `${randomUUID()}${ext}`; + const storagePath = join(dir, storedName); + const buffer = Buffer.from(await file.arrayBuffer()); + await writeFile(storagePath, buffer); + + const [saved] = await db.insert(transactionAttachments).values({ + filename: file.name, + mime_type: file.type || guessMimeType(file.name), + size_bytes: buffer.length, + storage_path: storagePath, + }).returning(); + + if (saved) { + result.push({ + id: saved.id, + filename: saved.filename, + mime_type: saved.mime_type, + size_bytes: saved.size_bytes, + }); + } + } + + return c.json({ attachments: result }, 201); + }); + + // GET /attachments/:id โ€” serve/download an attachment + router.get('/attachments/:id', async (c) => { + const attachmentId = c.req.param('id'); + + const attachment = await db.query.transactionAttachments.findFirst({ + where: eq(transactionAttachments.id, attachmentId), + }); + + if (!attachment) { + throw new HTTPException(404, { message: 'Attachment not found' }); + } + + if (!existsSync(attachment.storage_path)) { + throw new HTTPException(404, { message: 'Attachment file not found on disk' }); + } + + const disposition = c.req.query('download') === 'true' ? 'attachment' : 'inline'; + const stream = createReadStream(attachment.storage_path); + + return new Response(stream as any, { + status: 200, + headers: { + 'Content-Type': attachment.mime_type, + 'Content-Disposition': `${disposition}; filename="${encodeURIComponent(attachment.filename)}"`, + 'Content-Length': String(attachment.size_bytes), + }, + }); + }); + + // GET /tickets/:id/attachments โ€” list attachments for a ticket + router.get('/tickets/:id/attachments', async (c) => { + const ticketId = Number(c.req.param('id')); + + const ticket = await db.query.tickets.findFirst({ + where: eq(tickets.id, ticketId), + }); + + if (!ticket) { + throw new HTTPException(404, { message: 'Ticket not found' }); + } + + const ticketTransactions = await db.query.transactions.findMany({ + where: eq(transactions.ticket_id, ticketId), + }); + + const txIds = ticketTransactions.map((tx) => tx.id); + if (txIds.length === 0) { + return c.json([]); + } + + const attachments = await Promise.all( + txIds.map((txId) => + db.query.transactionAttachments.findMany({ + where: eq(transactionAttachments.transaction_id, txId), + }) + ) + ); + + return c.json(attachments.flat()); + }); + + return router; +} diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 0000000..68e2b2c --- /dev/null +++ b/src/routes/auth.ts @@ -0,0 +1,132 @@ +import { Hono } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import { z } from 'zod/v4'; +import type { Db } from '../db/index.ts'; +import { users, apiTokens } from '../db/schema.ts'; +import { eq, desc, sql } from 'drizzle-orm'; +import { createToken, createAuthMiddleware } from '../auth/middleware.ts'; + +const LoginSchema = z.object({ + username: z.string().min(1), + password: z.string().min(1), +}); + +export function createAuthRouter(db: Db): Hono { + const router = new Hono(); + const { requireAuth } = createAuthMiddleware(db); + + // POST /auth/login + router.post('/auth/login', async (c) => { + const body = await c.req.json(); + const parsed = LoginSchema.parse(body); + + const user = await db.query.users.findFirst({ + where: eq(users.username, parsed.username), + }); + + if (!user || !user.password_hash) { + throw new HTTPException(401, { message: 'Invalid username or password' }); + } + + const valid = await Bun.password.verify(parsed.password, user.password_hash); + if (!valid) { + throw new HTTPException(401, { message: 'Invalid username or password' }); + } + + const token = await createToken({ + id: user.id, + username: user.username, + role: user.role, + }); + + return c.json({ + token, + user: { + id: user.id, + username: user.username, + email: user.email, + role: user.role, + }, + }); + }); + + // GET /auth/me โ€” return current user from token + router.get('/auth/me', requireAuth, async (c) => { + const authUser = c.get('user'); + const user = await db.query.users.findFirst({ + where: eq(users.id, authUser.userId), + }); + + if (!user) { + throw new HTTPException(404, { message: 'User not found' }); + } + + return c.json({ + id: user.id, + username: user.username, + email: user.email, + role: user.role, + }); + }); + + // POST /auth/tokens โ€” create API token + router.post('/auth/tokens', requireAuth, async (c) => { + const body = await c.req.json(); + const name = String(body.name || 'API token').trim(); + const authUser = c.get('user'); + + const rawToken = `tessera_${crypto.randomUUID().replace(/-/g, '')}`; + const tokenHash = await Bun.password.hash(rawToken); + + const [token] = await db.insert(apiTokens).values({ + user_id: authUser.userId, + name, + token_hash: tokenHash, + }).returning(); + + if (!token) { + throw new HTTPException(500, { message: 'Failed to create token' }); + } + + return c.json({ + id: token.id, + name: token.name, + token: rawToken, + created_at: token.created_at, + }, 201); + }); + + // GET /auth/tokens โ€” list tokens + router.get('/auth/tokens', requireAuth, async (c) => { + const authUser = c.get('user'); + const result = await db.query.apiTokens.findMany({ + where: eq(apiTokens.user_id, authUser.userId), + orderBy: desc(apiTokens.created_at), + }); + return c.json(result.map((t) => ({ + id: t.id, + name: t.name, + last_used_at: t.last_used_at, + created_at: t.created_at, + }))); + }); + + // DELETE /auth/tokens/:id โ€” revoke token + router.delete('/auth/tokens/:id', requireAuth, async (c) => { + const id = c.req.param('id'); + const authUser = c.get('user'); + + // Verify ownership before revoke + const allTokens = await db.query.apiTokens.findMany(); + const existing = allTokens.find((t) => t.id === id && t.user_id === authUser.userId); + if (!existing) { + throw new HTTPException(404, { message: 'Token not found' }); + } + + // Raw delete to avoid Drizzle type issue with new apiTokens table + await db.execute(sql`DELETE FROM api_tokens WHERE id = ${id}`); + return c.json({ ok: true }); + }); + + return router; +} diff --git a/src/routes/dashboards.ts b/src/routes/dashboards.ts index 50ba7cd..6fb9b20 100644 --- a/src/routes/dashboards.ts +++ b/src/routes/dashboards.ts @@ -276,6 +276,30 @@ export function createDashboardsRouter(db: Db): Hono { } } + // Widget-level filters override or add to view filters + const widgetFilters = (widget.config as Record)?.filters as Array<{ field: string; operator: string; value: string }> | undefined; + if (widgetFilters) { + for (const f of widgetFilters) { + if (f.field === 'status') { + if (f.operator === 'is_not') result = result.filter((t) => t.status !== f.value); + else result = result.filter((t) => t.status === f.value); + } else if (f.field === 'queue') { + if (f.operator === 'is_not') result = result.filter((t) => t.queue_id !== f.value); + else result = result.filter((t) => t.queue_id === f.value); + } else if (f.field === 'owner') { + if (f.value === 'unassigned') result = result.filter((t) => !t.owner_id); + else result = result.filter((t) => t.owner_id === f.value); + } else if (f.field === 'q') { + const q = f.value.toLowerCase(); + result = result.filter((t) => + t.subject.toLowerCase().includes(q) || + String(t.id).includes(q) || + (queueName.get(t.queue_id) ?? '').toLowerCase().includes(q) + ); + } + } + } + const limit = (widget.config as Record)?.limit as number ?? 5; // Find lifecycle for status classification @@ -337,6 +361,61 @@ export function createDashboardsRouter(db: Db): Hono { return c.json({ type: 'status_chart', counts, total: result.length, title: widget.title, view_id: view.id }); } + case 'my_tickets': { + const authUser = c.get('user'); + const myTickets = result.filter((t) => t.owner_id === authUser.userId); + return c.json({ type: 'my_tickets', total: myTickets.length, title: widget.title, view_id: view.id }); + } + + case 'trend_chart': { + const period = ((widget.config as Record)?.period as string) ?? 'day'; + const days = (widget.config as Record)?.days as number ?? 30; + const trendField = ((widget.config as Record)?.field as string) ?? 'created_at'; + const now = new Date(); + const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1000); + + const filtered = result.filter((t) => { + const d = trendField === 'updated_at' ? t.updated_at : t.created_at; + return d && new Date(d) >= start; + }); + + const points: Record = {}; + for (const t of filtered) { + const d = new Date(trendField === 'updated_at' ? t.updated_at! : t.created_at!); + let key: string; + if (period === 'week') { + const weekStart = new Date(d); + weekStart.setDate(d.getDate() - d.getDay()); + key = weekStart.toISOString().slice(0, 10); + } else { + key = d.toISOString().slice(0, 10); // YYYY-MM-DD + } + points[key] = (points[key] ?? 0) + 1; + } + + return c.json({ type: 'trend_chart', counts: points, total: result.length, title: widget.title, view_id: view.id }); + } + + case 'overdue': { + const dateFieldKey = (widget.config as Record)?.field_key as string; + const now = new Date(); + const overdue = result.filter((t) => { + if (!dateFieldKey) { + // No specific field โ€” check if any inactive-adjacent status + const lc = lifecycleByQueue.get(t.queue_id); + if (lc) { + const inactive = lc.statuses.inactive; + if (inactive.includes(t.status)) return false; // already resolved + } + // Check if updated_at is older than 7 days + const updated = t.updated_at ? new Date(t.updated_at) : new Date(0); + return (now.getTime() - updated.getTime()) > 7 * 24 * 60 * 60 * 1000; + } + return false; // Would need CF value lookup for date field + }); + return c.json({ type: 'overdue', total: overdue.length, title: widget.title, view_id: view.id }); + } + case 'grouped_counts': { const groupBy = (widget.config as Record)?.group_by as string ?? 'owner'; const groups: Record = {}; diff --git a/src/routes/notifications.ts b/src/routes/notifications.ts new file mode 100644 index 0000000..3d7b84b --- /dev/null +++ b/src/routes/notifications.ts @@ -0,0 +1,64 @@ +import { Hono } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import type { Db } from '../db/index.ts'; +import { notifications } from '../db/schema.ts'; +import { and, eq, desc } from 'drizzle-orm'; + +export function createNotificationsRouter(db: Db): Hono { + const router = new Hono(); + + // GET /notifications โ€” list notifications for current user + router.get('/notifications', async (c) => { + const user = c.get('user'); + const result = await db.query.notifications.findMany({ + where: eq(notifications.user_id, user.userId), + orderBy: desc(notifications.created_at), + // Return last 50 + }); + return c.json(result.slice(0, 50)); + }); + + // GET /notifications/unread-count + router.get('/notifications/unread-count', async (c) => { + const user = c.get('user'); + const result = await db.query.notifications.findMany({ + where: and( + eq(notifications.user_id, user.userId), + eq(notifications.read, false), + ), + }); + return c.json({ count: result.length }); + }); + + // PATCH /notifications/:id/read โ€” mark as read + router.patch('/notifications/:id/read', async (c) => { + const id = c.req.param('id'); + await db.update(notifications).set({ read: true }).where(eq(notifications.id, id)); + return c.json({ ok: true }); + }); + + // PATCH /notifications/read-all โ€” mark all as read + router.patch('/notifications/read-all', async (c) => { + const user = c.get('user'); + await db.update(notifications) + .set({ read: true }) + .where(eq(notifications.user_id, user.userId)); + return c.json({ ok: true }); + }); + + return router; +} + +// Helper to create notifications (used by other routes) +export async function createNotification( + db: Db, + data: { user_id: string; ticket_id?: number; type: string; title: string; body?: string }, +) { + await db.insert(notifications).values({ + user_id: data.user_id, + ticket_id: data.ticket_id ?? null, + type: data.type, + title: data.title, + body: data.body ?? null, + }); +} diff --git a/src/routes/queue-permissions.ts b/src/routes/queue-permissions.ts new file mode 100644 index 0000000..79c1d79 --- /dev/null +++ b/src/routes/queue-permissions.ts @@ -0,0 +1,176 @@ +import { Hono } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import type { Db } from '../db/index.ts'; +import { queuePermissions, userPermissions, teams, queues, users } from '../db/schema.ts'; +import { eq } from 'drizzle-orm'; + +export function createQueuePermissionsRouter(db: Db): Hono { + const router = new Hono(); + + // GET /queue-permissions โ€” list all permissions (with team + queue names) + router.get('/queue-permissions', async (c) => { + const all = await db.query.queuePermissions.findMany(); + + // Enrich with names + const teamIds = [...new Set(all.map((p) => p.team_id))]; + const queueIds = [...new Set(all.map((p) => p.queue_id))]; + + const teamList = teamIds.length > 0 + ? await db.query.teams.findMany({ where: (t, { inArray }) => inArray(t.id, teamIds) }) + : []; + const queueList = queueIds.length > 0 + ? await db.query.queues.findMany({ where: (t, { inArray }) => inArray(t.id, queueIds) }) + : []; + + const teamById = new Map(teamList.map((t) => [t.id, t])); + const queueById = new Map(queueList.map((q) => [q.id, q])); + + const enriched = all.map((p) => ({ + ...p, + team_name: teamById.get(p.team_id)?.name ?? p.team_id, + queue_name: queueById.get(p.queue_id)?.name ?? p.queue_id, + })); + + return c.json(enriched); + }); + + // GET /queue-permissions/teams-and-queues โ€” return teams and queues for the form + router.get('/queue-permissions/teams-and-queues', async (c) => { + const [teamList, queueList] = await Promise.all([ + db.query.teams.findMany(), + db.query.queues.findMany(), + ]); + return c.json({ teams: teamList, queues: queueList }); + }); + + // POST /queue-permissions โ€” grant a right + router.post('/queue-permissions', async (c) => { + const body = await c.req.json(); + const { queue_id, team_id, right_name } = body; + + if (!queue_id || !team_id || !right_name) { + throw new HTTPException(422, { message: 'queue_id, team_id, and right_name are required' }); + } + + const validRights = ['ticket.view', 'ticket.create', 'ticket.reply', 'ticket.comment', 'ticket.modify', 'queue.admin']; + if (!validRights.includes(right_name)) { + throw new HTTPException(422, { message: `Invalid right. Must be one of: ${validRights.join(', ')}` }); + } + + // Check for duplicate + const existing = await db.query.queuePermissions.findFirst({ + where: (table, { and, eq: eqFn }) => + and( + eqFn(table.queue_id, queue_id), + eqFn(table.team_id, team_id), + eqFn(table.right_name, right_name), + ), + }); + + if (existing) { + return c.json(existing); // Idempotent โ€” return existing + } + + const [perm] = await db.insert(queuePermissions).values({ + queue_id, + team_id, + right_name, + }).returning(); + + if (!perm) { + throw new HTTPException(500, { message: 'Failed to create permission' }); + } + + return c.json(perm, 201); + }); + + // DELETE /queue-permissions/:id โ€” revoke a right + router.delete('/queue-permissions/:id', async (c) => { + const id = c.req.param('id'); + + const existing = await db.query.queuePermissions.findFirst({ + where: eq(queuePermissions.id, id), + }); + + if (!existing) { + throw new HTTPException(404, { message: 'Permission not found' }); + } + + await db.delete(queuePermissions).where(eq(queuePermissions.id, id)); + + return c.json({ ok: true }); + }); + + // GET /user-permissions โ€” list all per-user permissions + router.get('/user-permissions', async (c) => { + const all = await db.query.userPermissions.findMany(); + + const userIds = [...new Set(all.map((p) => p.user_id))]; + const queueIds = [...new Set(all.map((p) => p.queue_id))]; + + const userList = userIds.length > 0 + ? await db.query.users.findMany({ where: (t, { inArray }) => inArray(t.id, userIds) }) + : []; + const queueList = queueIds.length > 0 + ? await db.query.queues.findMany({ where: (t, { inArray }) => inArray(t.id, queueIds) }) + : []; + + const userById = new Map(userList.map((u) => [u.id, u])); + const queueById = new Map(queueList.map((q) => [q.id, q])); + + const enriched = all.map((p) => ({ + ...p, + username: userById.get(p.user_id)?.username ?? p.user_id, + queue_name: queueById.get(p.queue_id)?.name ?? p.queue_id, + })); + + return c.json(enriched); + }); + + // POST /user-permissions โ€” grant a right to a user + router.post('/user-permissions', async (c) => { + const body = await c.req.json(); + const { queue_id, user_id, right_name } = body; + + if (!queue_id || !user_id || !right_name) { + throw new HTTPException(422, { message: 'queue_id, user_id, and right_name are required' }); + } + + const validRights = ['ticket.view', 'ticket.create', 'ticket.reply', 'ticket.comment', 'ticket.modify', 'queue.admin']; + if (!validRights.includes(right_name)) { + throw new HTTPException(422, { message: `Invalid right. Must be one of: ${validRights.join(', ')}` }); + } + + const existing = await db.query.userPermissions.findFirst({ + where: (table, { and, eq: eqFn }) => + and( + eqFn(table.queue_id, queue_id), + eqFn(table.user_id, user_id), + eqFn(table.right_name, right_name), + ), + }); + + if (existing) return c.json(existing); + + const [perm] = await db.insert(userPermissions).values({ + queue_id, + user_id, + right_name, + }).returning(); + + if (!perm) { + throw new HTTPException(500, { message: 'Failed to create user permission' }); + } + + return c.json(perm, 201); + }); + + // DELETE /user-permissions/:id โ€” revoke a user right + router.delete('/user-permissions/:id', async (c) => { + const id = c.req.param('id'); + await db.delete(userPermissions).where(eq(userPermissions.id, id)); + return c.json({ ok: true }); + }); + + return router; +} diff --git a/src/routes/tickets.ts b/src/routes/tickets.ts index eed6766..b391a8a 100644 --- a/src/routes/tickets.ts +++ b/src/routes/tickets.ts @@ -1,7 +1,15 @@ import { Hono } from 'hono'; import { HTTPException } from 'hono/http-exception'; +import { existsSync, mkdirSync } from 'node:fs'; +import { join, extname } from 'node:path'; +import { writeFile } from 'node:fs/promises'; +import { randomUUID } from 'node:crypto'; import type { Db } from '../db/index.ts'; -import { tickets, transactions, customFieldValues, customFields, queues, lifecycles, queueCustomFields, teamMembers } from '../db/schema.ts'; +import { config } from '../config.ts'; +import { getUserId } from '../auth/middleware.ts'; +import { requireRight } from '../auth/permissions.ts'; +import { createNotification } from './notifications.ts'; +import { tickets, transactions, customFieldValues, customFields, queues, lifecycles, queueCustomFields, teamMembers, transactionAttachments, ticketLinks } from '../db/schema.ts'; import { and, eq, asc, or, ilike, isNull, inArray, exists, sql } from 'drizzle-orm'; import { CreateTicketSchema, UpdateTicketSchema, CommentSchema } from '../models/ticket.ts'; import { ScripEngine } from '../scrip/engine.ts'; @@ -22,8 +30,14 @@ export function createTicketsRouter(db: Db): Hono { // GET / โ€” list tickets router.get('/', async (c) => { - const params = new URL(c.req.url).searchParams; const queueId = c.req.query('queue_id'); + + // If filtering by queue, check view permission + if (queueId) { + await requireRight(c, db, queueId, 'ticket.view'); + } + + const params = new URL(c.req.url).searchParams; const status = c.req.query('status'); const ownerId = c.req.query('owner_id'); const teamId = c.req.query('team_id'); @@ -63,17 +77,70 @@ export function createTicketsRouter(db: Db): Hono { } } - // Text search: push to SQL via ilike on ticket columns + queue name join + // Subject filter: supports "contains:", "is:", "is_not:", "starts_with:" + const subjectFilter = c.req.query('subject'); + if (subjectFilter) { + const idx = subjectFilter.indexOf(':'); + const op = idx > -1 ? subjectFilter.slice(0, idx) : 'contains'; + const val = idx > -1 ? subjectFilter.slice(idx + 1) : subjectFilter; + if (val) { + if (op === 'is') conditions.push(eq(tickets.subject, val)); + else if (op === 'is_not') conditions.push(sql`${tickets.subject} != ${val}`); + else if (op === 'starts_with') conditions.push(ilike(tickets.subject, `${val}%`)); + else conditions.push(ilike(tickets.subject, `%${val}%`)); // contains + } + } + + // Date filters: format "before:YYYY-MM-DD" or "after:YYYY-MM-DD" + for (const [fieldName, column] of [['created', tickets.created_at], ['updated', tickets.updated_at]] as const) { + const dateFilter = c.req.query(fieldName); + if (dateFilter) { + const idx = dateFilter.indexOf(':'); + const op = idx > -1 ? dateFilter.slice(0, idx) : 'after'; + const val = idx > -1 ? dateFilter.slice(idx + 1) : dateFilter; + if (val) { + if (op === 'before') conditions.push(sql`${column} <= ${val}::timestamptz`); + else conditions.push(sql`${column} >= ${val}::timestamptz`); // after + } + } + } + + // Text search across tickets, transactions, queue names, and custom fields if (query) { const pattern = `%${query}%`; conditions.push( or( ilike(tickets.subject, pattern), - ilike(tickets.status, pattern), - sql`${tickets.id}::text ILIKE ${pattern}` + sql`${tickets.id}::text ILIKE ${pattern}`, + // Queue name + exists( + db.select({ n: sql`1` }) + .from(queues) + .where(and( + eq(queues.id, tickets.queue_id), + ilike(queues.name, pattern) + )) + ), + // Transaction bodies (comments, correspondence) + exists( + db.select({ n: sql`1` }) + .from(transactions) + .where(and( + eq(transactions.ticket_id, tickets.id), + sql`transactions.data->>'body' ILIKE ${pattern}` + )) + ), + // Custom field values + exists( + db.select({ n: sql`1` }) + .from(customFieldValues) + .where(and( + eq(customFieldValues.ticket_id, tickets.id), + ilike(customFieldValues.value, pattern) + )) + ) )! ); - // Queue name search requires join โ€” keep as post-filter } // Custom field filters: use EXISTS subquery @@ -100,19 +167,9 @@ export function createTicketsRouter(db: Db): Hono { limit, }); - // Post-filter for queue name text search (requires in-memory join) - let filtered = result; - if (query) { - const queuesForSearch = await db.query.queues.findMany(); - const queueNameById = new Map(queuesForSearch.map((queue) => [queue.id, queue.name])); - filtered = result.filter((ticket) => - (queueNameById.get(ticket.queue_id) ?? '').toLowerCase().includes(query.toLowerCase()) - ); - } - // Attach custom field values to all tickets - if (filtered.length > 0) { - const ticketIds = filtered.map((t) => t.id); + if (result.length > 0) { + const ticketIds = result.map((t) => t.id); const allCfValues = await db.query.customFieldValues.findMany({ where: (table, { inArray }) => inArray(table.ticket_id, ticketIds), }); @@ -124,7 +181,7 @@ export function createTicketsRouter(db: Db): Hono { : []; const fieldMap = new Map(allFields.map((f) => [f.id, f])); - const ticketsWithCf = filtered.map((ticket) => { + const ticketsWithCf = result.map((ticket) => { const cfs = allCfValues .filter((v) => v.ticket_id === ticket.id) .map((v) => ({ @@ -149,14 +206,16 @@ export function createTicketsRouter(db: Db): Hono { return c.json(ticketsWithCf); } - return c.json(filtered); + return c.json(result); }); // POST / โ€” create ticket router.post('/', async (c) => { const body = await c.req.json(); const parsed = CreateTicketSchema.parse(body); - const creatorId = '00000000-0000-0000-0000-000000000000'; + + await requireRight(c, db, parsed.queue_id, 'ticket.create'); + const creatorId = getUserId(c); const customFieldInput = parsed.custom_fields ?? {}; const customFieldEntries = Object.entries(customFieldInput) .map(([fieldId, value]) => [fieldId, value.trim()] as const) @@ -208,6 +267,21 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(422, { message: `${field.name}: value is not an allowed option` }); } } + if (field.field_type === 'date') { + if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { + throw new HTTPException(422, { message: `${field.name}: value must be a date in YYYY-MM-DD format` }); + } + const parsed = new Date(value); + if (isNaN(parsed.getTime())) { + throw new HTTPException(422, { message: `${field.name}: invalid date` }); + } + } + if (field.field_type === 'datetime') { + const parsed = new Date(value); + if (isNaN(parsed.getTime())) { + throw new HTTPException(422, { message: `${field.name}: value must be a valid ISO 8601 datetime` }); + } + } if (field.pattern) { const regex = new RegExp(field.pattern); if (!regex.test(value)) { @@ -285,6 +359,8 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(404, { message: 'Ticket not found' }); } + await requireRight(c, db, ticket.queue_id, 'ticket.view'); + const cfValues = await db.query.customFieldValues.findMany({ where: eq(customFieldValues.ticket_id, id), }); @@ -301,10 +377,32 @@ export function createTicketsRouter(db: Db): Hono { custom_field: cfMap.get(v.custom_field_id) ?? null, })); - return c.json({ ...ticket, custom_fields: customFieldsMapped }); - }); + // Blocking dependencies: tickets this one DependsOn that aren't resolved yet + const dependsOnLinks = await db.query.ticketLinks.findMany({ + where: (t, { and, eq: eqFn }) => + and(eqFn(t.ticket_id, id), eqFn(t.link_type, 'DependsOn')), + }); + const blockingIds = dependsOnLinks.map((l) => l.target_ticket_id); + let blockedBy: Array<{ id: number; subject: string; status: string }> = []; + if (blockingIds.length > 0) { + const blockingTickets = await db.query.tickets.findMany({ + where: (t, { inArray }) => inArray(t.id, blockingIds), + }); + const queue = await db.query.queues.findFirst({ + where: eq(queues.id, ticket.queue_id), + }); + let inactiveStatuses: string[] = []; + if (queue?.lifecycle_id) { + const lc = await db.query.lifecycles.findFirst({ where: eq(lifecycles.id, queue.lifecycle_id) }); + if (lc) inactiveStatuses = (lc.definition as any)?.statuses?.inactive ?? []; + } + blockedBy = blockingTickets + .filter((t) => !inactiveStatuses.includes(t.status)) + .map((t) => ({ id: t.id, subject: t.subject, status: t.status })); + } - // PATCH /:id โ€” update ticket + return c.json({ ...ticket, custom_fields: customFieldsMapped, blocked_by: blockedBy }); + }); router.patch('/:id', async (c) => { const id = Number(c.req.param('id')); const body = await c.req.json(); @@ -318,6 +416,8 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(404, { message: 'Ticket not found' }); } + await requireRight(c, db, ticket.queue_id, 'ticket.modify'); + let lifecycleDef: LifecycleDefinition | null = null; // Validate lifecycle transition if status is changing @@ -337,6 +437,34 @@ export function createTicketsRouter(db: Db): Hono { if (!result.valid) { throw new HTTPException(422, { message: result.error ?? 'Invalid transition' }); } + + // Check transition-gating right + if (result.requiredRight) { + await requireRight(c, db, ticket.queue_id, result.requiredRight as any); + } + + // Check dependency enforcement: can't resolve/close if this ticket DependsOn unresolved tickets + const inactiveStatuses = lifecycleDef.statuses.inactive; + if (inactiveStatuses.includes(parsed.status)) { + const dependsOnLinks = await db.query.ticketLinks.findMany({ + where: (t, { and, eq: eqFn }) => + and( + eqFn(t.ticket_id, id), + eqFn(t.link_type, 'DependsOn'), + ), + }); + + for (const link of dependsOnLinks) { + const target = await db.query.tickets.findFirst({ + where: eq(tickets.id, link.target_ticket_id), + }); + if (target && !inactiveStatuses.includes(target.status)) { + throw new HTTPException(422, { + message: `Cannot resolve: this ticket depends on ticket ${target.id} (${target.subject}) which is still ${target.status}. Resolve or close that ticket first.`, + }); + } + } + } } } } @@ -350,7 +478,7 @@ export function createTicketsRouter(db: Db): Hono { field: 'subject', old_value: ticket.subject, new_value: parsed.subject, - creator_id: '00000000-0000-0000-0000-000000000000', + creator_id: getUserId(c), }); } @@ -361,7 +489,7 @@ export function createTicketsRouter(db: Db): Hono { field: 'status', old_value: ticket.status, new_value: parsed.status, - creator_id: '00000000-0000-0000-0000-000000000000', + creator_id: getUserId(c), }); } @@ -372,7 +500,7 @@ export function createTicketsRouter(db: Db): Hono { field: 'owner_id', old_value: ticket.owner_id ?? null, new_value: parsed.owner_id, - creator_id: '00000000-0000-0000-0000-000000000000', + creator_id: getUserId(c), }); } @@ -383,7 +511,7 @@ export function createTicketsRouter(db: Db): Hono { field: 'team_id', old_value: (ticket as any).team_id ?? null, new_value: parsed.team_id, - creator_id: '00000000-0000-0000-0000-000000000000', + creator_id: getUserId(c), }); } @@ -425,10 +553,24 @@ export function createTicketsRouter(db: Db): Hono { await db.insert(transactions).values(txList as any); } - // Run scrips - const prepared = await scripEngine.prepare(id, txList as any); + // Run scrips โ€” use TransactionBatch when multiple changes, TransactionCreate for single + const stage = txList.length > 1 ? 'TransactionBatch' as const : 'TransactionCreate' as const; + const prepared = await scripEngine.prepare(id, txList as any, stage); const results = await scripEngine.commit(prepared); + // Notify on assignment change + if (parsed.owner_id !== undefined && parsed.owner_id !== ticket.owner_id) { + if (parsed.owner_id) { + await createNotification(db, { + user_id: parsed.owner_id, + ticket_id: id, + type: 'assigned', + title: `You were assigned to ticket ${id}`, + body: ticket.subject, + }); + } + } + return c.json({ ticket: updated, scrip_results: results }); }); @@ -446,6 +588,8 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(404, { message: 'Ticket not found' }); } + await requireRight(c, db, ticket.queue_id, 'ticket.modify'); + if (parsed.status) { const queue = await db.query.queues.findFirst({ where: eq(queues.id, ticket.queue_id), @@ -470,13 +614,13 @@ export function createTicketsRouter(db: Db): Hono { if (parsed.status && parsed.status !== ticket.status) { txList.push({ - id: '00000000-0000-0000-0000-000000000000', + id: getUserId(c), ticket_id: id, transaction_type: 'StatusChange', field: 'status', old_value: ticket.status, new_value: parsed.status, - creator_id: '00000000-0000-0000-0000-000000000000', + creator_id: getUserId(c), }); } @@ -487,7 +631,7 @@ export function createTicketsRouter(db: Db): Hono { return c.json({ prepared_scrips: results }); }); - // GET /:id/transactions โ€” list transactions for ticket + // GET /:id/transactions โ€” list transactions for ticket (with attachments) router.get('/:id/transactions', async (c) => { const id = Number(c.req.param('id')); @@ -496,7 +640,105 @@ export function createTicketsRouter(db: Db): Hono { orderBy: asc(transactions.created_at), }); - return c.json(result); + // Fetch attachments for these transactions + const txIds = result.map((tx) => tx.id); + if (txIds.length > 0) { + const attachments = await db.query.transactionAttachments.findMany({ + where: inArray(transactionAttachments.transaction_id, txIds), + }); + + const attachmentsByTxId = new Map(); + for (const att of attachments) { + if (!att.transaction_id) continue; + const list = attachmentsByTxId.get(att.transaction_id); + if (list) { + list.push(att); + } else { + attachmentsByTxId.set(att.transaction_id, [att]); + } + } + + const resultWithAttachments = result.map((tx) => ({ + ...tx, + attachments: attachmentsByTxId.get(tx.id) ?? [], + })); + + return c.json(resultWithAttachments); + } + + return c.json(result.map((tx) => ({ ...tx, attachments: [] }))); + }); + + // POST /:id/attachments โ€” upload file attachments for a ticket + router.post('/:id/attachments', async (c) => { + const ticketId = Number(c.req.param('id')); + + const ticket = await db.query.tickets.findFirst({ + where: eq(tickets.id, ticketId), + }); + + if (!ticket) { + throw new HTTPException(404, { message: 'Ticket not found' }); + } + + await requireRight(c, db, ticket.queue_id, 'ticket.reply'); + + const formData = await c.req.formData(); + const files = formData.getAll('files') as File[]; + + if (files.length === 0) { + throw new HTTPException(422, { message: 'No files provided' }); + } + + const now = new Date(); + const year = now.getFullYear().toString(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const dir = join(config.UPLOAD_DIR, year, month); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + const MIME_MAP: Record = { + '.txt': 'text/plain', '.html': 'text/html', '.css': 'text/css', + '.js': 'application/javascript', '.json': 'application/json', '.xml': 'application/xml', + '.pdf': 'application/pdf', '.png': 'image/png', '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', + '.webp': 'image/webp', '.zip': 'application/zip', '.gz': 'application/gzip', + '.csv': 'text/csv', '.mp4': 'video/mp4', '.mp3': 'audio/mpeg', + '.md': 'text/markdown', '.yaml': 'text/yaml', '.yml': 'text/yaml', + }; + + const result: Array<{ id: string; filename: string; mime_type: string; size_bytes: number }> = []; + + for (const file of files) { + if (!(file instanceof File)) continue; + + const ext = extname(file.name).toLowerCase(); + const storedName = `${randomUUID()}${ext}`; + const storagePath = join(dir, storedName); + const buffer = Buffer.from(await file.arrayBuffer()); + await writeFile(storagePath, buffer); + + const mimeType = file.type || MIME_MAP[ext] || 'application/octet-stream'; + + const [saved] = await db.insert(transactionAttachments).values({ + filename: file.name, + mime_type: mimeType, + size_bytes: buffer.length, + storage_path: storagePath, + }).returning(); + + if (saved) { + result.push({ + id: saved.id, + filename: saved.filename, + mime_type: saved.mime_type, + size_bytes: saved.size_bytes, + }); + } + } + + return c.json({ attachments: result }, 201); }); // POST /:id/comment โ€” add a comment (reply or internal note) @@ -513,12 +755,23 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(404, { message: 'Ticket not found' }); } + await requireRight(c, db, ticket.queue_id, parsed.internal ? 'ticket.comment' : 'ticket.reply'); + const transactionType = parsed.internal ? 'Comment' : 'Correspond'; + const attachmentIds = parsed.attachment_ids ?? []; + + const txData: Record = { body: parsed.body }; + if (attachmentIds.length > 0) { + txData.attachment_ids = attachmentIds; + } + + const timeWorked = parsed.time_worked_minutes ?? 0; const [tx] = await db.insert(transactions).values({ ticket_id: id, transaction_type: transactionType, - data: { body: parsed.body }, + data: txData, + time_worked_minutes: timeWorked, creator_id: parsed.creator_id, }).returning(); @@ -526,14 +779,454 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(500, { message: 'Failed to create comment' }); } + // Link pre-uploaded attachment records to this transaction + if (attachmentIds.length > 0) { + await db.update(transactionAttachments) + .set({ transaction_id: tx.id }) + .where(inArray(transactionAttachments.id, attachmentIds)); + } + // Run scrips const txList = [tx]; const prepared = await scripEngine.prepare(id, txList as any); await scripEngine.commit(prepared); + // Notify ticket owner and creator + const commenterId = getUserId(c); + const notifyTargets = new Set([ticket.owner_id, ticket.creator_id].filter(Boolean) as string[]); + notifyTargets.delete(commenterId); + for (const userId of notifyTargets) { + await createNotification(db, { + user_id: userId, + ticket_id: id, + type: 'commented', + title: `New ${transactionType === 'Comment' ? 'internal note' : 'reply'} on ticket ${id}`, + body: parsed.body.slice(0, 200), + }); + } + return c.json(tx, 201); }); + // GET /:id/links โ€” list links for a ticket (with target ticket info) + router.get('/:id/links', async (c) => { + const id = Number(c.req.param('id')); + + const ticket = await db.query.tickets.findFirst({ + where: eq(tickets.id, id), + }); + + if (!ticket) { + throw new HTTPException(404, { message: 'Ticket not found' }); + } + + await requireRight(c, db, ticket.queue_id, 'ticket.view'); + + const links = await db.query.ticketLinks.findMany({ + where: eq(ticketLinks.ticket_id, id), + orderBy: asc(ticketLinks.created_at), + }); + + // Enrich with target ticket info + const targetIds = [...new Set(links.map((l) => l.target_ticket_id))]; + const targetTickets = targetIds.length > 0 + ? await db.query.tickets.findMany({ + where: (table, { inArray }) => inArray(table.id, targetIds), + }) + : []; + const ticketById = new Map(targetTickets.map((t) => [t.id, t])); + + const enriched = links.map((link) => { + const target = ticketById.get(link.target_ticket_id); + return { + ...link, + target_ticket: target + ? { id: target.id, subject: target.subject, status: target.status } + : null, + }; + }); + + return c.json(enriched); + }); + + // POST /:id/links โ€” create a link to another ticket + router.post('/:id/links', async (c) => { + const id = Number(c.req.param('id')); + const body = await c.req.json(); + const targetTicketId = Number(body.target_ticket_id); + const linkType = String(body.link_type || 'RelatedTo'); + + if (!targetTicketId || isNaN(targetTicketId)) { + throw new HTTPException(422, { message: 'target_ticket_id is required' }); + } + + if (targetTicketId === id) { + throw new HTTPException(422, { message: 'Cannot link a ticket to itself' }); + } + + const validTypes = ['DependsOn', 'Blocks', 'RefersTo', 'RelatedTo', 'Duplicates', 'MemberOf']; + if (!validTypes.includes(linkType)) { + throw new HTTPException(422, { message: `Invalid link_type. Must be one of: ${validTypes.join(', ')}` }); + } + + const ticket = await db.query.tickets.findFirst({ + where: eq(tickets.id, id), + }); + + if (!ticket) { + throw new HTTPException(404, { message: 'Ticket not found' }); + } + + await requireRight(c, db, ticket.queue_id, 'ticket.modify'); + + const target = await db.query.tickets.findFirst({ + where: eq(tickets.id, targetTicketId), + }); + + if (!target) { + throw new HTTPException(404, { message: 'Target ticket not found' }); + } + + // Check for duplicate + const existing = await db.query.ticketLinks.findFirst({ + where: (table, { and, eq: eqFn }) => + and( + eqFn(table.ticket_id, id), + eqFn(table.target_ticket_id, targetTicketId), + eqFn(table.link_type, linkType), + ), + }); + + if (existing) { + throw new HTTPException(422, { message: 'This link already exists' }); + } + + const creatorId = body.creator_id || getUserId(c); + + const [link] = await db.insert(ticketLinks).values({ + ticket_id: id, + target_ticket_id: targetTicketId, + link_type: linkType, + creator_id: creatorId, + }).returning(); + + if (!link) { + throw new HTTPException(500, { message: 'Failed to create link' }); + } + + // Create transactions on both tickets + const linkData = { + link_type: linkType, + link_id: link.id, + target_ticket_id: targetTicketId, + target_subject: target.subject, + }; + + const reverseLinkData = { + link_type: linkType, + link_id: link.id, + target_ticket_id: id, + target_subject: ticket.subject, + }; + + const [txSource] = await db.insert(transactions).values({ + ticket_id: id, + transaction_type: 'LinkCreate', + field: linkType, + old_value: null, + new_value: String(targetTicketId), + data: linkData, + creator_id: creatorId, + }).returning(); + + const [txTarget] = await db.insert(transactions).values({ + ticket_id: targetTicketId, + transaction_type: 'LinkCreate', + field: linkType, + old_value: null, + new_value: String(id), + data: reverseLinkData, + creator_id: creatorId, + }).returning(); + + // Run scrips on source ticket + if (txSource) { + const prepared = await scripEngine.prepare(id, [txSource] as any); + await scripEngine.commit(prepared); + } + + // Run scrips on target ticket + if (txTarget) { + const prepared = await scripEngine.prepare(targetTicketId, [txTarget] as any); + await scripEngine.commit(prepared); + } + + // Include target ticket info in response + return c.json({ + ...link, + target_ticket: { id: target.id, subject: target.subject, status: target.status }, + }, 201); + }); + + // DELETE /:id/links/:linkId โ€” remove a link + router.delete('/:id/links/:linkId', async (c) => { + const id = Number(c.req.param('id')); + const linkId = c.req.param('linkId'); + + const ticket = await db.query.tickets.findFirst({ + where: eq(tickets.id, id), + }); + + if (!ticket) { + throw new HTTPException(404, { message: 'Ticket not found' }); + } + + await requireRight(c, db, ticket.queue_id, 'ticket.modify'); + + const link = await db.query.ticketLinks.findFirst({ + where: eq(ticketLinks.id, linkId), + }); + + if (!link || link.ticket_id !== id) { + throw new HTTPException(404, { message: 'Link not found' }); + } + + await db.delete(ticketLinks).where(eq(ticketLinks.id, linkId)); + + const target = await db.query.tickets.findFirst({ + where: eq(tickets.id, link.target_ticket_id), + }); + + const creatorId = getUserId(c); + + const [txSource] = await db.insert(transactions).values({ + ticket_id: id, + transaction_type: 'LinkDelete', + field: link.link_type, + old_value: String(link.target_ticket_id), + new_value: null, + data: { + link_type: link.link_type, + target_ticket_id: link.target_ticket_id, + target_subject: target?.subject ?? 'unknown', + }, + creator_id: creatorId, + }).returning(); + + // Run scrips on source ticket + if (txSource) { + const prepared = await scripEngine.prepare(id, [txSource] as any); + await scripEngine.commit(prepared); + } + + return c.json({ ok: true }); + }); + + // POST /:id/merge โ€” merge this ticket into another + router.post('/:id/merge', async (c) => { + const id = Number(c.req.param('id')); + const body = await c.req.json(); + const targetId = Number(body.target_ticket_id); + + if (!targetId || isNaN(targetId) || targetId === id) { + throw new HTTPException(422, { message: 'target_ticket_id must be a different ticket ID' }); + } + + const source = await db.query.tickets.findFirst({ where: eq(tickets.id, id) }); + if (!source) throw new HTTPException(404, { message: 'Source ticket not found' }); + + const target = await db.query.tickets.findFirst({ where: eq(tickets.id, targetId) }); + if (!target) throw new HTTPException(404, { message: 'Target ticket not found' }); + + await requireRight(c, db, source.queue_id, 'ticket.modify'); + await requireRight(c, db, target.queue_id, 'ticket.modify'); + + const creatorId = getUserId(c); + + // Move transactions + await db.update(transactions) + .set({ ticket_id: targetId } as any) + .where(eq(transactions.ticket_id, id)); + + // Move attachments + const sourceTxs = await db.query.transactions.findMany({ where: eq(transactions.ticket_id, id) }); + // (attachments are linked via transaction_id which stays the same, no-op) + + // Move custom field values + const sourceCfs = await db.query.customFieldValues.findMany({ + where: eq(customFieldValues.ticket_id, id), + }); + for (const cf of sourceCfs) { + const existing = await db.query.customFieldValues.findFirst({ + where: (t, { and, eq: eqFn }) => + and( + eqFn(t.custom_field_id, cf.custom_field_id), + eqFn(t.ticket_id, targetId), + eqFn(t.value, cf.value), + ), + }); + if (existing) { + await db.delete(customFieldValues).where(eq(customFieldValues.id, cf.id)); + } else { + await db.update(customFieldValues) + .set({ ticket_id: targetId } as any) + .where(eq(customFieldValues.id, cf.id)); + } + } + + // Move ticket links (update source references to target) + await db.update(ticketLinks) + .set({ ticket_id: targetId } as any) + .where(eq(ticketLinks.ticket_id, id)); + // Update links pointing TO this ticket to point to target instead + await db.update(ticketLinks) + .set({ target_ticket_id: targetId } as any) + .where(eq(ticketLinks.target_ticket_id, id)); + + // Close the source ticket + await db.update(tickets).set({ + status: 'closed', + updated_at: new Date(), + } as any).where(eq(tickets.id, id)); + + // Create merge transactions on both tickets + await db.insert(transactions).values({ + ticket_id: targetId, + transaction_type: 'Comment', + data: { body: `Ticket ${source.id} (${source.subject}) was merged into this ticket.` }, + creator_id: creatorId, + }); + + await db.insert(transactions).values({ + ticket_id: id, + transaction_type: 'StatusChange', + field: 'status', + old_value: source.status, + new_value: 'closed', + data: { merged_into: targetId, body: `Merged into ticket ${targetId} (${target.subject}).` }, + creator_id: creatorId, + }); + + // Create a duplicate link + await db.insert(ticketLinks).values({ + ticket_id: id, + target_ticket_id: targetId, + link_type: 'Duplicates', + creator_id: creatorId, + }).onConflictDoNothing(); + + return c.json({ ok: true, target_id: targetId }); + }); + + // POST /batch โ€” bulk update tickets + router.post('/batch', async (c) => { + const body = await c.req.json(); + const ticketIds: number[] = (body.ticket_ids ?? []).map(Number).filter((n: number) => !isNaN(n) && n > 0); + const { status, owner_id, team_id } = body; + + if (ticketIds.length === 0) { + throw new HTTPException(422, { message: 'ticket_ids is required and must be an array of ticket IDs' }); + } + + if (ticketIds.length > 100) { + throw new HTTPException(422, { message: 'Maximum 100 tickets per batch update' }); + } + + const results: Array<{ id: number; ok: boolean; error?: string }> = []; + + for (const id of ticketIds) { + const ticket = await db.query.tickets.findFirst({ where: eq(tickets.id, id) }); + if (!ticket) { + results.push({ id, ok: false, error: 'Ticket not found' }); + continue; + } + + await requireRight(c, db, ticket.queue_id, 'ticket.modify'); + + try { + const txList: any[] = []; + const updateData: Record = { updated_at: new Date() }; + + if (status !== undefined && status !== ticket.status) { + // Validate lifecycle transition + const queue = await db.query.queues.findFirst({ where: eq(queues.id, ticket.queue_id) }); + if (queue?.lifecycle_id) { + const lifecycle = await db.query.lifecycles.findFirst({ where: eq(lifecycles.id, queue.lifecycle_id) }); + if (lifecycle) { + const lifecycleDef = lifecycle.definition as LifecycleDefinition; + const result = lifecycleValidator.validateTransition(lifecycleDef, ticket.status, status); + if (!result.valid) { + results.push({ id, ok: false, error: result.error ?? 'Invalid transition' }); + continue; + } + // Dependency enforcement + const inactiveStatuses = lifecycleDef.statuses.inactive; + if (inactiveStatuses.includes(status)) { + const dependsOnLinks = await db.query.ticketLinks.findMany({ + where: (t, { and, eq: eqFn }) => and(eqFn(t.ticket_id, id), eqFn(t.link_type, 'DependsOn')), + }); + let blocked = false; + for (const link of dependsOnLinks) { + const target = await db.query.tickets.findFirst({ where: eq(tickets.id, link.target_ticket_id) }); + if (target && !inactiveStatuses.includes(target.status)) { + results.push({ id, ok: false, error: `Blocked by ticket ${target.id} (${target.subject}) โ€” still ${target.status}` }); + blocked = true; + break; + } + } + if (blocked) continue; + } + } + } + txList.push({ + ticket_id: id, + transaction_type: 'StatusChange', + field: 'status', + old_value: ticket.status, + new_value: status, + creator_id: getUserId(c), + }); + updateData.status = status; + } + + if (owner_id !== undefined && owner_id !== ticket.owner_id) { + txList.push({ + ticket_id: id, + transaction_type: 'SetOwner', + field: 'owner_id', + old_value: ticket.owner_id ?? null, + new_value: owner_id, + creator_id: getUserId(c), + }); + updateData.owner_id = owner_id; + } + + if (team_id !== undefined && team_id !== (ticket as any).team_id) { + txList.push({ + ticket_id: id, + transaction_type: 'SetTeam', + field: 'team_id', + old_value: (ticket as any).team_id ?? null, + new_value: team_id, + creator_id: getUserId(c), + }); + updateData.team_id = team_id; + } + + await db.update(tickets).set(updateData as any).where(eq(tickets.id, id)); + if (txList.length > 0) { + await db.insert(transactions).values(txList as any); + } + + results.push({ id, ok: true }); + } catch (err) { + results.push({ id, ok: false, error: err instanceof Error ? err.message : String(err) }); + } + } + + return c.json({ results }); + }); + // PATCH /:id/custom-fields/:fieldId โ€” set or clear a custom field value router.patch('/:id/custom-fields/:fieldId', async (c) => { const id = Number(c.req.param('id')); @@ -549,6 +1242,8 @@ export function createTicketsRouter(db: Db): Hono { throw new HTTPException(404, { message: 'Ticket not found' }); } + await requireRight(c, db, ticket.queue_id, 'ticket.modify'); + const assignment = await db.query.queueCustomFields.findFirst({ where: and( eq(queueCustomFields.queue_id, ticket.queue_id), @@ -575,6 +1270,22 @@ export function createTicketsRouter(db: Db): Hono { } } + if (value && field.field_type === 'date') { + if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { + throw new HTTPException(422, { message: `${field.name}: value must be a date in YYYY-MM-DD format` }); + } + const parsed = new Date(value); + if (isNaN(parsed.getTime())) { + throw new HTTPException(422, { message: `${field.name}: invalid date` }); + } + } + if (value && field.field_type === 'datetime') { + const parsed = new Date(value); + if (isNaN(parsed.getTime())) { + throw new HTTPException(422, { message: `${field.name}: value must be a valid ISO 8601 datetime` }); + } + } + if (value && field.pattern) { const regex = new RegExp(field.pattern); if (!regex.test(value)) { @@ -613,7 +1324,7 @@ export function createTicketsRouter(db: Db): Hono { field: field.key, old_value: oldValue || null, new_value: value || null, - creator_id: '00000000-0000-0000-0000-000000000000', + creator_id: getUserId(c), }).returning(); const prepared = await scripEngine.prepare(id, [tx] as any); diff --git a/src/routes/users.ts b/src/routes/users.ts index fa8be72..9bdd204 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -18,6 +18,8 @@ export function createUsersRouter(db: Db): Hono { const body = await c.req.json(); const username = String(body.username ?? '').trim(); const email = body.email ? String(body.email).trim() : null; + const role = body.role ? String(body.role) : 'staff'; + const password = body.password ? String(body.password).trim() : null; if (!username) { throw new HTTPException(400, { message: 'username is required' }); @@ -26,6 +28,8 @@ export function createUsersRouter(db: Db): Hono { const [user] = await db.insert(users).values({ username, email, + role, + password_hash: password ? await Bun.password.hash(password) : null, }).returning(); if (!user) { @@ -50,6 +54,10 @@ export function createUsersRouter(db: Db): Hono { const updateData: Partial = {}; if (body.username !== undefined) updateData.username = String(body.username).trim(); if (body.email !== undefined) updateData.email = body.email ? String(body.email).trim() : null; + if (body.role !== undefined) updateData.role = String(body.role); + if (body.password !== undefined && String(body.password).trim()) { + updateData.password_hash = await Bun.password.hash(String(body.password).trim()); + } const [updated] = await db.update(users) .set(updateData) diff --git a/src/scrip/conditions.ts b/src/scrip/conditions.ts index eacfc28..796c3d8 100644 --- a/src/scrip/conditions.ts +++ b/src/scrip/conditions.ts @@ -5,6 +5,7 @@ import type { LifecycleDefinition } from '../lifecycle/validator.ts'; export interface ConditionEvaluateContext { lifecycleDef?: LifecycleDefinition; + customFields?: Record; // key โ†’ value map of CF values } export interface ConditionConfig { @@ -16,6 +17,7 @@ export interface ConditionConfig { old_value?: unknown; new_value?: unknown; value?: unknown; + link_type?: unknown; } export interface ConditionEvaluator { @@ -82,11 +84,52 @@ export class OnCustomFieldChange implements ConditionEvaluator { } } +export class OnLinkCreate implements ConditionEvaluator { + evaluate(_ticket: Ticket, transactions: Transaction[], _context?: ConditionEvaluateContext, config?: ConditionConfig): boolean { + return transactions.some((tx) => { + if (tx.transaction_type !== 'LinkCreate') return false; + if (config?.link_type) { + const linkType = tx.field; + if (!matchesStatusFilter(linkType, config.link_type)) return false; + } + return true; + }); + } +} + +export class OnOverdue implements ConditionEvaluator { + evaluate(_ticket: Ticket, _transactions: Transaction[], context?: ConditionEvaluateContext, config?: ConditionConfig): boolean { + const fieldKey = config?.field_key ?? config?.field_id ?? config?.field; + if (!fieldKey) return false; + + const cfValue = context?.customFields?.[String(fieldKey)]; + if (!cfValue) return false; + + // Parse the date value + const dueDate = new Date(cfValue); + if (isNaN(dueDate.getTime())) return false; + + // Check if overdue (past due date) + if (new Date() <= dueDate) return false; + + // Check that ticket is still active (not in inactive state) + const lifecycleDef = context?.lifecycleDef; + if (lifecycleDef) { + const inactiveStates = lifecycleDef.statuses.inactive; + if (inactiveStates.includes(_ticket.status)) return false; + } + + return true; + } +} + const conditionRegistry: Record = { OnCreate: new OnCreate(), OnStatusChange: new OnStatusChange(), OnResolve: new OnResolve(), OnCustomFieldChange: new OnCustomFieldChange(), + OnLinkCreate: new OnLinkCreate(), + OnOverdue: new OnOverdue(), }; export function getConditionEvaluator(type: string): ConditionEvaluator | null { diff --git a/src/scrip/engine.ts b/src/scrip/engine.ts index 6e6f910..bc7d7d9 100644 --- a/src/scrip/engine.ts +++ b/src/scrip/engine.ts @@ -37,6 +37,7 @@ export class ScripEngine { async prepare( ticketId: number, transactions: Transaction[], + stage: 'TransactionCreate' | 'TransactionBatch' = 'TransactionCreate', ): Promise { const ticketRecord = await this.db.query.tickets.findFirst({ where: eq(tickets.id, ticketId), @@ -53,6 +54,15 @@ export class ScripEngine { const matchingScrips = allScrips.filter((scrip) => { if (scrip.disabled) return false; if (scrip.queue_id !== null && scrip.queue_id !== ticketRecord.queue_id) return false; + if (scrip.stage !== stage) return false; + // Filter by applicable transaction types โ€” if set, at least one tx must match + if (scrip.applicable_trans_types) { + const types = scrip.applicable_trans_types.split(',').map((t) => t.trim()).filter(Boolean); + if (types.length > 0 && !types.includes('Any')) { + const txTypes = new Set(transactions.map((tx) => tx.transaction_type)); + if (!types.some((t) => txTypes.has(t))) return false; + } + } return true; }); @@ -70,10 +80,6 @@ export class ScripEngine { } } - const conditionContext: ConditionEvaluateContext = { - lifecycleDef, - }; - const cfValues = await this.db.query.customFieldValues.findMany({ where: eq(customFieldValues.ticket_id, ticketId), }); @@ -94,6 +100,11 @@ export class ScripEngine { } } + const conditionContext: ConditionEvaluateContext = { + lifecycleDef, + customFields: customFieldsMap, + }; + const prepared: PreparedScrip[] = []; for (const scrip of matchingScrips) { diff --git a/src/scrip/scheduler.ts b/src/scrip/scheduler.ts new file mode 100644 index 0000000..67f797a --- /dev/null +++ b/src/scrip/scheduler.ts @@ -0,0 +1,92 @@ +import type { Db } from '../db/index.ts'; +import { transactions, tickets, queues, lifecycles } from '../db/schema.ts'; +import { eq, and, isNull } from 'drizzle-orm'; +import { ScripEngine } from './engine.ts'; + +const SYSTEM_USER = '00000000-0000-0000-0000-000000000000'; + +/** + * Run scheduled scrips against all active tickets. + * Creates a synthetic "Scheduled" transaction so conditions like OnOverdue can fire. + */ +export async function runScheduledScrips(db: Db): Promise<{ checked: number; fired: number }> { + const engine = new ScripEngine(db); + + // Get all lifecycles to determine inactive statuses + const allLifecycles = await db.query.lifecycles.findMany(); + const inactiveByQueue = new Map>(); + + // Get all queues with lifecycles + const allQueues = await db.query.queues.findMany(); + for (const q of allQueues) { + if (q.lifecycle_id) { + const lc = allLifecycles.find((l) => l.id === q.lifecycle_id); + if (lc) { + const def = lc.definition as any; + inactiveByQueue.set(q.id, new Set(def?.statuses?.inactive ?? ['resolved', 'closed'])); + } + } + } + + // Find all potentially active tickets + const allTickets = await db.query.tickets.findMany(); + const active = allTickets.filter((t) => { + const inactive = inactiveByQueue.get(t.queue_id); + if (inactive) return !inactive.has(t.status); + return !['resolved', 'closed'].includes(t.status); + }); + + let fired = 0; + + for (const ticket of active) { + try { + // Create a synthetic Scheduled transaction + const [tx] = await db.insert(transactions).values({ + ticket_id: ticket.id, + transaction_type: 'Comment' as any, + field: 'scheduled', + data: { body: 'Scheduled scrip evaluation' }, + creator_id: SYSTEM_USER, + } as any).returning(); + + if (!tx) continue; + + // Run scrips + const prepared = await engine.prepare(ticket.id, [tx as any]); + if (prepared.length > 0) { + const results = await engine.commit(prepared); + const successes = results.filter((r) => r.success); + if (successes.length > 0) fired += successes.length; + } + } catch (err) { + // Log and continue โ€” don't let one failing ticket block the scheduler + console.error(`[scheduler] Error processing ticket ${ticket.id}:`, err instanceof Error ? err.message : String(err)); + } + } + + return { checked: active.length, fired }; +} + +/** + * Start the background scheduler. Runs every `intervalMinutes` minutes. + */ +export function startScheduler(db: Db, intervalMinutes = 5) { + console.log(`[scheduler] Starting scrip scheduler (every ${intervalMinutes}m)`); + + const run = async () => { + try { + const result = await runScheduledScrips(db); + if (result.fired > 0) { + console.log(`[scheduler] Checked ${result.checked} tickets, fired ${result.fired} scrip actions`); + } + } catch (err) { + console.error('[scheduler] Error:', err instanceof Error ? err.message : String(err)); + } + }; + + // Run once at startup after a short delay + setTimeout(run, 10000); + + // Then run on interval + setInterval(run, intervalMinutes * 60 * 1000); +} diff --git a/web/src/app/admin/page-content.tsx b/web/src/app/admin/page-content.tsx index ff1acbb..4949354 100644 --- a/web/src/app/admin/page-content.tsx +++ b/web/src/app/admin/page-content.tsx @@ -73,6 +73,7 @@ import { removeTeamMember, } from "@/lib/api"; import type { Queue, Ticket, Lifecycle, LifecycleDefinition, Scrip, Template, CustomField, QueueCustomField, TemplatePreview, User, Team } from "@/lib/types"; +import { ScripWizard } from "@/components/scrip-wizard"; import { cn } from "@/lib/utils"; function AdminHeader() { @@ -802,6 +803,7 @@ return { message: "Metadata fetched" };`); const [disabled, setDisabled] = useState(false); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); + const [wizardOpen, setWizardOpen] = useState(false); const fetchScrips = useCallback(async () => { setLoading(true); @@ -1189,10 +1191,15 @@ return { message: "Metadata fetched" };`); Build automations visually, then fine-tune the exact action payload in JSON.

- +
+ + +
{loading ? ( @@ -1701,6 +1708,24 @@ return { message: "Metadata fetched" };`); )} + setWizardOpen(false)} + error={saveError} + onCreate={async (data) => { + // Strip nulls for Zod optional fields + const payload = Object.fromEntries( + Object.entries(data).filter(([, v]) => v !== null) + ); + const { error: createErr } = await createScrip(payload as any); + if (createErr) { setSaveError(createErr); return; } + setWizardOpen(false); + await fetchScrips(); + }} + queues={queues} + customFields={customFields} + templates={templates} + /> ); } diff --git a/web/src/app/dashboards/[id]/page.tsx b/web/src/app/dashboards/[id]/page.tsx index 6227430..83b8c25 100644 --- a/web/src/app/dashboards/[id]/page.tsx +++ b/web/src/app/dashboards/[id]/page.tsx @@ -41,6 +41,7 @@ import { CountWidget } from "@/components/widgets/count-widget"; import { TicketListWidget } from "@/components/widgets/ticket-list-widget"; import { StatusChartWidget } from "@/components/widgets/status-chart-widget"; import { GroupedCountsWidget } from "@/components/widgets/grouped-counts-widget"; +import { TrendChartWidget } from "@/components/widgets/trend-chart-widget"; import { cn } from "@/lib/utils"; function widgetGridStyle(position: { x: number; y: number; w: number; h: number }) { @@ -331,13 +332,18 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string switch (widget.data.type) { case "count": + case "my_tickets": return ; + case "overdue": + return ; case "ticket_list": return ; case "status_chart": return ; case "grouped_counts": return ; + case "trend_chart": + return ; default: return (
@@ -554,6 +560,9 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string + + +
{addType === "grouped_counts" && ( diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 4db0e67..6bab278 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -4,6 +4,7 @@ import { Suspense } from "react"; import { ThemeProvider } from "next-themes"; import "./globals.css"; import { AppShell } from "@/components/app-shell"; +import { AuthProvider } from "@/lib/auth-context"; const ibmPlexSans = IBM_Plex_Sans({ subsets: ["latin"], @@ -31,9 +32,11 @@ export default function RootLayout({ style={{ fontSize: "15px", lineHeight: 1.5 }} > - Loading...}> - {children} - + + Loading...}> + {children} + + diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx new file mode 100644 index 0000000..1184d5b --- /dev/null +++ b/web/src/app/login/page.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/lib/auth-context"; +import { LogInIcon } from "lucide-react"; + +export default function LoginPage() { + const router = useRouter(); + const { login, user } = useAuth(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + // Already logged in + if (user) { + router.replace("/"); + return null; + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!username.trim() || !password) return; + setLoading(true); + setError(null); + + const result = await login(username.trim(), password); + setLoading(false); + + if (result) { + setError(result); + } else { + router.push("/"); + } + }; + + return ( +
+
+
+

Tessera

+

Sign in to your account

+
+ +
+
+ + setUsername(e.target.value)} + autoComplete="username" + autoFocus + className="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus:border-ring" + /> +
+
+ + setPassword(e.target.value)} + autoComplete="current-password" + className="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus:border-ring" + /> +
+ + {error &&

{error}

} + + +
+ +

+ Demo: admin / admin +

+
+
+ ); +} diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 1acde3f..5e09a69 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -7,6 +7,7 @@ import { ArrowDownAZIcon, CheckCircle2Icon, ChevronRightIcon, + DownloadIcon, GaugeIcon, LayoutGridIcon, LayoutListIcon, @@ -18,8 +19,8 @@ import { XIcon, } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; -import { createTicket, getCustomFields, getLifecycles, getQueueCustomFields, getQueues, getTickets, getUsers, getViews, createView, deleteView, getDashboards, updateTicket } from "@/lib/api"; -import type { CustomField, Lifecycle, Queue, QueueCustomField, SavedView, Ticket, User } from "@/lib/types"; +import { createTicket, getCustomFields, getLifecycles, getQueueCustomFields, getQueues, getTickets, getUsers, getViews, createView, deleteView, getDashboards, updateTicket, batchUpdateTickets, getTeams } from "@/lib/api"; +import type { CustomField, Lifecycle, Queue, QueueCustomField, SavedView, Team, Ticket, User } from "@/lib/types"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -30,6 +31,8 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { cn, formatTicketId } from "@/lib/utils"; +import { SearchableSelect } from "@/components/searchable-select"; +import { LayoutBuilder, type SubtitleEntry } from "@/components/layout-builder"; const STATUS_META: Record = { new: { label: "New", color: "#64748b", tone: "bg-slate-500/10 text-slate-700 dark:text-slate-300" }, @@ -51,35 +54,22 @@ type SortKey = "updated" | "created" | "id"; interface ColumnConfig { key: string; label: string; - width: number; // px - visible: boolean; + width: number; } -const ALL_COLUMNS: ColumnConfig[] = [ - { key: "id", label: "ID", width: 100, visible: true }, - { key: "subject", label: "Subject", width: 320, visible: true }, - { key: "status", label: "Status", width: 120, visible: true }, - { key: "queue", label: "Queue", width: 140, visible: true }, - { key: "owner", label: "Owner", width: 130, visible: true }, - { key: "created", label: "Created", width: 130, visible: false }, - { key: "updated", label: "Updated", width: 130, visible: false }, +const ALL_FIELDS: ColumnConfig[] = [ + { key: "id", label: "ID", width: 100 }, + { key: "subject", label: "Subject", width: 400 }, + { key: "status", label: "Status", width: 120 }, + { key: "queue", label: "Queue", width: 140 }, + { key: "owner", label: "Owner", width: 130 }, + { key: "created", label: "Created", width: 130 }, + { key: "updated", label: "Updated", width: 130 }, + { key: "team", label: "Team", width: 130 }, ]; -function baseColumns(): ColumnConfig[] { - return [ - { key: "id", label: "ID", width: 100, visible: true }, - { key: "subject", label: "Subject", width: 400, visible: true }, - { key: "status", label: "Status", width: 120, visible: true }, - { key: "queue", label: "Queue", width: 140, visible: true }, - { key: "owner", label: "Owner", width: 130, visible: true }, - { key: "created", label: "Created", width: 130, visible: false }, - { key: "updated", label: "Updated", width: 130, visible: false }, - ]; -} - -function defaultColumns(): ColumnConfig[] { - return baseColumns().map((c) => ({ ...c })); -} +const DEFAULT_ROW1 = ["id", "subject", "status"]; +const DEFAULT_ROW2 = ["queue", "owner"]; const LS_KEY = "tessera_columns"; @@ -105,6 +95,22 @@ function queueName(queues: Queue[], queueId: string) { return queues.find((queue) => queue.id === queueId)?.name ?? queueId.slice(0, 8); } +function getSubtitleValue(key: string, ticket: Ticket, context: { users: User[]; queues: Queue[]; teamsList: Team[] }): string | null { + if (key === "subject") return null; + if (key === "id") return formatTicketId(ticket.id); + if (key === "status") return statusLabel(ticket.status); + if (key === "queue") return context.queues.find((q) => q.id === ticket.queue_id)?.name ?? ticket.queue_id.slice(0, 8); + if (key === "owner") return ticket.owner_id ? (context.users.find((u) => u.id === ticket.owner_id)?.username ?? "assigned") : "Unassigned"; + if (key === "team") return context.teamsList.find((t) => t.id === ticket.team_id)?.name ?? null; + if (key === "created") return relativeTime(ticket.created_at); + if (key === "updated") return relativeTime(ticket.updated_at); + if (key.startsWith("cf.")) { + const cfKey = key.slice(3); + return ticket.custom_fields?.find((v) => v.custom_field?.key === cfKey || v.custom_field?.name === cfKey)?.value ?? null; + } + return null; +} + function relativeTime(value: string) { return formatDistanceToNow(new Date(value), { addSuffix: true }); } @@ -178,8 +184,10 @@ function TicketWorkbenchContent() { const [queues, setQueues] = useState([]); const [lifecycles, setLifecycles] = useState([]); const [users, setUsers] = useState([]); + const [teamsList, setTeamsList] = useState([]); const [customFields, setCustomFields] = useState([]); const [clock, setClock] = useState(0); + const [initialLoad, setInitialLoad] = useState(true); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); @@ -187,47 +195,82 @@ function TicketWorkbenchContent() { const [batchSaving, setBatchSaving] = useState(false); const [searchQuery, setSearchQuery] = useState(""); + const [debouncedQuery, setDebouncedQuery] = useState(""); + const searchRef = useRef(searchQuery); + searchRef.current = searchQuery; + // Debounce search: update debouncedQuery 300ms after user stops typing + useEffect(() => { + const timer = setTimeout(() => setDebouncedQuery(searchQuery), 300); + return () => clearTimeout(timer); + }, [searchQuery]); const [filters, setFilters] = useState([]); - const [columns, setColumns] = useState(() => { - if (typeof window === "undefined") return defaultColumns(); + const [row1Keys, setRow1Keys] = useState(() => { + if (typeof window === "undefined") return DEFAULT_ROW1; try { const stored = localStorage.getItem(LS_KEY); - if (stored) return JSON.parse(stored) as ColumnConfig[]; + if (stored) { + const parsed = JSON.parse(stored); + if (parsed.row1) return parsed.row1 as string[]; + // Migrate old format + if (Array.isArray(parsed)) { + return parsed.filter((c: any) => c.visible !== false && c.display !== "subtitle" && c.display !== "hidden").map((c: any) => c.key); + } + } } catch { /* ignore */ } - return defaultColumns(); + return DEFAULT_ROW1; + }); + const [row2Entries, setRow2Entries] = useState(() => { + if (typeof window === "undefined") return DEFAULT_ROW2.map((k) => ({ key: k, under: k })); + try { + const stored = localStorage.getItem(LS_KEY); + if (stored) { + const parsed = JSON.parse(stored); + if (parsed.row2Entries) return parsed.row2Entries as SubtitleEntry[]; + if (parsed.row2 && Array.isArray(parsed.row2)) { + // Migrate: old flat keys โ†’ entries with self as under + if (typeof parsed.row2[0] === "string") return parsed.row2.map((k: string) => ({ key: k, under: k })); + return parsed.row2 as SubtitleEntry[]; + } + } + } catch { /* ignore */ } + return DEFAULT_ROW2.map((k) => ({ key: k, under: k })); }); const [density, setDensity] = useState("comfortable"); const [sortKey, setSortKey] = useState("updated"); const [resizingCol, setResizingCol] = useState(null); + const [colWidths, setColWidths] = useState>({}); const [colPickerOpen, setColPickerOpen] = useState(false); - // Persist columns to localStorage + // Persist layout to localStorage useEffect(() => { - try { localStorage.setItem(LS_KEY, JSON.stringify(columns)); } catch { /* ignore */ } - }, [columns]); + try { localStorage.setItem(LS_KEY, JSON.stringify({ row1: row1Keys, row2Entries })); } catch { /* ignore */ } + }, [row1Keys, row2Entries]); - // Build available columns: base + custom fields - const availableColumns = useMemo(() => { - const base = baseColumns(); - const cfCols: ColumnConfig[] = customFields + // Build available fields: base + custom fields + const allFields = useMemo(() => { + const cfFields: ColumnConfig[] = customFields .filter((cf) => cf.key) - .map((cf) => ({ - key: `cf.${cf.key}`, - label: cf.name, - width: 140, - visible: columns.find((c) => c.key === `cf.${cf.key}`)?.visible ?? false, - })); - // Merge with current visibility state - const merged = base.map((bc) => { - const current = columns.find((c) => c.key === bc.key); - return current ?? bc; - }); - for (const cf of cfCols) { - const current = columns.find((c) => c.key === cf.key); - merged.push(current ?? cf); - } - return merged; - }, [customFields, columns]); + .map((cf) => ({ key: `cf.${cf.key}`, label: cf.name, width: 140 })); + return [...ALL_FIELDS, ...cfFields]; + }, [customFields]); + + const fieldByKey = useMemo(() => { + const map = new Map(); + for (const f of allFields) map.set(f.key, f); + return map; + }, [allFields]); + + const row1Fields = row1Keys.map((k) => fieldByKey.get(k)).filter(Boolean) as ColumnConfig[]; + const row2EntriesResolved = row2Entries.filter((e) => fieldByKey.has(e.key)); + // Group subtitle entries by which column they sit under + const subsByColumn = new Map(); + for (const e of row2EntriesResolved) { + const list = subsByColumn.get(e.under) ?? []; + list.push(e); + subsByColumn.set(e.under, list); + } + + const colWidth = (key: string, fallback: number) => colWidths[key] ?? fallback; // Saved views const [savedViewsList, setSavedViewsList] = useState([]); @@ -268,6 +311,9 @@ function TicketWorkbenchContent() { const apiStatus = filters.find((f) => f.field === "status")?.value; const apiOwner = filters.find((f) => f.field === "owner")?.value; const apiQueue = filters.find((f) => f.field === "queue")?.value; + const apiSubject = filters.find((f) => f.field === "subject"); + const apiCreated = filters.find((f) => f.field === "created"); + const apiUpdated = filters.find((f) => f.field === "updated"); const customFieldFilters: Record = {}; for (const f of filters) { if (f.field.startsWith("cf.")) { @@ -276,19 +322,23 @@ function TicketWorkbenchContent() { } const routeTeamId = searchParams.get("team_id") ?? ""; - const [ticketsRes, queuesRes, usersRes, fieldsRes, lifecycleRes] = await Promise.all([ + const [ticketsRes, queuesRes, usersRes, fieldsRes, lifecycleRes, teamsRes] = await Promise.all([ getTickets({ - q: searchQuery.trim() || undefined, + q: debouncedQuery.trim() || undefined, status: apiStatus || undefined, queue_id: activeQueue || apiQueue || undefined, owner_id: apiOwner || undefined, team_id: routeTeamId || undefined, custom_fields: Object.keys(customFieldFilters).length > 0 ? customFieldFilters : undefined, + subject: apiSubject ? `${apiSubject.operator}:${apiSubject.value}` : undefined, + created: apiCreated ? `${apiCreated.operator}:${apiCreated.value}` : undefined, + updated: apiUpdated ? `${apiUpdated.operator}:${apiUpdated.value}` : undefined, }), getQueues(), getUsers(), getCustomFields(), getLifecycles(), + getTeams(), ]); if (ticketsRes.error) { @@ -314,6 +364,12 @@ function TicketWorkbenchContent() { setUsers(usersRes.data ?? []); } + if (teamsRes?.error) { + setError((current) => current ?? teamsRes.error); + } else if (teamsRes?.data) { + setTeamsList(teamsRes.data); + } + if (fieldsRes.error) { setError((current) => current ?? fieldsRes.error); } else { @@ -327,10 +383,11 @@ function TicketWorkbenchContent() { } setLoading(false); + setInitialLoad(false); setRefreshing(false); setClock(fetchedAt); }, - [filters, newQueueId, routeQueue, searchQuery] + [filters, newQueueId, routeQueue, debouncedQuery] ); useEffect(() => { @@ -422,7 +479,12 @@ function TicketWorkbenchContent() { ); if (view.sort_key) setSortKey(view.sort_key as SortKey); if (view.columns && Array.isArray(view.columns) && view.columns.length > 0) { - setColumns(view.columns as ColumnConfig[]); + // Load row1/row2 from saved view columns if available, else fall back to default + const cols = view.columns as any[]; + const r1 = cols.filter((c: any) => c.display !== "subtitle" && c.visible !== false).map((c: any) => c.key); + const r2 = cols.filter((c: any) => c.display === "subtitle").map((c: any) => c.key); + if (r1.length > 0) setRow1Keys(r1); + if (r2.length > 0) setRow2Entries(r2.map((k: string) => ({ key: k, under: k }))); } } }); @@ -430,7 +492,8 @@ function TicketWorkbenchContent() { // User navigated away from a view โ€” clear filters and reset columns setFilters([]); setSearchQuery(""); - setColumns(defaultColumns()); + setRow1Keys(DEFAULT_ROW1); + setRow2Entries(DEFAULT_ROW2.map((k) => ({ key: k, under: k }))); } }, [searchParams]); @@ -485,7 +548,6 @@ function TicketWorkbenchContent() { }, [clock, inactiveStatuses, tickets]); const filteredTickets = useMemo(() => { - const query = searchQuery.trim().toLowerCase(); const now = clock || 0; const queue = routeQueue; const statusFilterValue = filters.find((f) => f.field === "status")?.value; @@ -506,13 +568,7 @@ function TicketWorkbenchContent() { if (statusFilterValue && ticket.status !== statusFilterValue) return false; if (queueFilterValue && ticket.queue_id !== queueFilterValue) return false; if (queue && ticket.queue_id !== queue) return false; - if (!query) return true; - return ( - ticket.subject.toLowerCase().includes(query) || - formatTicketId(ticket.id).toLowerCase().includes(query) || - statusLabel(ticket.status).toLowerCase().includes(query) || - queueName(queues, ticket.queue_id).toLowerCase().includes(query) - ); + return true; }) .sort((a, b) => { if (sortKey === "id") return b.id - a.id; @@ -520,7 +576,7 @@ function TicketWorkbenchContent() { const bDate = sortKey === "created" ? b.created_at : b.updated_at; return new Date(bDate).getTime() - new Date(aDate).getTime(); }); - }, [clock, filters, queues, routeQueue, searchQuery, sortKey, tickets, view]); + }, [clock, filters, queues, routeQueue, sortKey, tickets, view]); @@ -538,24 +594,19 @@ function TicketWorkbenchContent() { } }; - const handleBatchStatus = async (newStatus: string) => { + const handleBatchAction = async (update: { status?: string; owner_id?: string | null; team_id?: string | null }) => { setBatchSaving(true); - for (const id of batchIds) { - await updateTicket(id, { status: newStatus }); - } + const ids = Array.from(batchIds); + const { data, error } = await batchUpdateTickets({ ticket_ids: ids, ...update }); setBatchSaving(false); - setBatchIds(new Set()); - await fetchData(); - }; - - const handleBatchAssign = async () => { - const me = users[0]?.id; - if (!me) return; - setBatchSaving(true); - for (const id of batchIds) { - await updateTicket(id, { owner_id: me }); + if (!error && data) { + const failed = data.results.filter((r) => !r.ok); + if (failed.length > 0) { + setError(`${failed.length} of ${ids.length} tickets failed to update`); + } + } else if (error) { + setError(error); } - setBatchSaving(false); setBatchIds(new Set()); await fetchData(); }; @@ -572,30 +623,27 @@ function TicketWorkbenchContent() { e.stopPropagation(); setResizingCol(leftKey); const startX = e.clientX; - const leftCol = columns.find((c) => c.key === leftKey); - const rightCol = rightKey ? columns.find((c) => c.key === rightKey) : null; - const leftStart = leftCol?.width ?? 140; - const rightStart = rightCol?.width ?? 140; + const leftField = fieldByKey.get(leftKey); + const rightField = rightKey ? fieldByKey.get(rightKey) : null; + const leftStart = colWidths[leftKey] ?? leftField?.width ?? 140; + const rightStart = rightField ? (colWidths[rightField.key] ?? rightField.width) : 140; const onMove = (ev: MouseEvent) => { const delta = ev.clientX - startX; const newLeft = Math.max(50, Math.min(800, leftStart + delta)); - const newRight = rightCol ? Math.max(50, Math.min(800, rightStart - delta)) : undefined; - setColumns((prev) => - prev.map((c) => { - if (c.key === leftKey) return { ...c, width: newLeft }; - if (rightCol && c.key === rightCol.key) return { ...c, width: newRight! }; - return c; - }) - ); + const newRight = rightField ? Math.max(50, Math.min(800, rightStart - delta)) : undefined; + setColWidths((prev) => { + const next = { ...prev, [leftKey]: newLeft }; + if (rightField && newRight !== undefined) next[rightField.key] = newRight; + return next; + }); }; + const onUp = () => { + setResizingCol(null); document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); - document.body.classList.remove("select-none"); - setResizingCol(null); }; - document.body.classList.add("select-none"); document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); }; @@ -659,7 +707,7 @@ function TicketWorkbenchContent() { if (data) router.push(`/tickets/${data.ticket.id}`); }; - if (loading) return ; + if (initialLoad && loading) return ; return (
@@ -676,6 +724,36 @@ function TicketWorkbenchContent() {
+
@@ -830,7 +908,7 @@ function TicketWorkbenchContent() { ))} -
+
+ {filters.length > 0 && ( + + )}
@@ -881,6 +968,35 @@ function TicketWorkbenchContent() { ) : ( <> + {batchIds.size > 0 && ( +
+ {batchIds.size} selected + + + +
+ )} {/* Table layout for consistent column alignment */}
{/* Column header */} @@ -889,11 +1005,11 @@ function TicketWorkbenchContent() { density === "compact" ? "min-h-7" : "min-h-8" )} style={{ display: "table-row" }}>
- {availableColumns.filter((c) => c.visible).map((col, idx, arr) => ( + {row1Fields.map((col, idx, arr) => (
{/* Resize handle: drags the boundary, resizes column to the LEFT */} {idx > 0 && ( @@ -910,6 +1026,39 @@ function TicketWorkbenchContent() {
+ {/* Subtitle header โ€” labels for each row2 field under its matching column */} + {row2EntriesResolved.length > 0 && ( +
+
+ {row1Fields.map((col) => { + const subsHere = subsByColumn.get(col.key) ?? []; + const orphans = col.key === "subject" ? row2EntriesResolved.filter((e) => !row1Fields.some((rf) => rf.key === e.under)) : []; + if (subsHere.length > 0 || orphans.length > 0) { + return ( +
+
+ {subsHere.map((e) => { + const f = fieldByKey.get(e.key); + return {f?.label ?? e.key}; + })} + {orphans.map((e) => { + const f = fieldByKey.get(e.key); + return {f?.label ?? e.key}; + })} +
+
+ ); + } + return
; + })} +
+
+ )} + {filteredTickets.map((ticket) => { const selected = false; const ownerName = ticket.owner_id @@ -946,10 +1095,10 @@ function TicketWorkbenchContent() { title={statusLabel(ticket.status)} />
- {availableColumns.filter((c) => c.visible).map((col) => { + {row1Fields.map((col) => { const cellStyle = { display: "table-cell" as const, - width: col.width, + width: colWidth(col.key, col.width), verticalAlign: "middle" as const, padding: density === "compact" ? "4px 12px" : "8px 12px", }; @@ -957,64 +1106,121 @@ function TicketWorkbenchContent() { if (col.key.startsWith("cf.")) { const cfKey = col.key.slice(3); const cfValue = ticket.custom_fields?.find((v) => v.custom_field?.key === cfKey || v.custom_field?.name === cfKey)?.value; + const cfSubs = subsByColumn.get(col.key) ?? []; return (
{cfValue ?? "โ€”"} + {density === "comfortable" && cfSubs.map((e) =>
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
)}
); } switch (col.key) { - case "id": + case "id": { + const idSubs = subsByColumn.get("id") ?? []; return (
{formatTicketId(ticket.id)} + {density === "comfortable" && idSubs.map((e) =>
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
)}
); - case "subject": + } + case "subject": { + // Subtitle fields that don't have a matching row1 column + // Subtitle under subject + orphans (under column not in row1) + const subsHere = row2EntriesResolved.filter((e) => + e.under === "subject" || !row1Fields.some((rf) => rf.key === e.under) + ); + const subParts: string[] = []; + const ctx = { users, queues, teamsList }; + for (const e of subsHere) { + const v = getSubtitleValue(e.key, ticket, ctx); + if (v) subParts.push(v); + } return ( -
+
{ticket.subject} - {density === "comfortable" && ( - - {ownerName ?? "Unassigned"} - - Created {relativeTime(ticket.created_at)} - + {density === "comfortable" && subParts.length > 0 && ( +
+ {subParts.map((part, i) => ( + + {i > 0 && } + {part} + + ))} +
)}
); + } case "status": + const statusSubs = subsByColumn.get("status") ?? []; return (
+ {density === "comfortable" && statusSubs.map((e) => ( +
+ {getSubtitleValue(e.key, ticket, { users, queues, teamsList })} +
+ ))}
); - case "queue": + case "queue": { + const subs = subsByColumn.get("queue") ?? []; return (
{queueName(queues, ticket.queue_id)} + {density === "comfortable" && subs.map((e) => ( +
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
+ ))}
); - case "owner": + } + case "owner": { + const subs = subsByColumn.get("owner") ?? []; return (
{ownerName ?? "โ€”"} + {density === "comfortable" && subs.map((e) => ( +
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
+ ))}
); - case "created": + } + case "created": { + const subs = subsByColumn.get("created") ?? []; return (
{relativeTime(ticket.created_at)} + {density === "comfortable" && subs.map((e) => ( +
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
+ ))}
); - case "updated": + } + case "updated": { + const subs = subsByColumn.get("updated") ?? []; return (
{relativeTime(ticket.updated_at)} + {density === "comfortable" && subs.map((e) => ( +
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
+ ))}
); + } + case "team": { + const subs = subsByColumn.get("team") ?? []; + return ( +
+ {teamsList.find((t) => t.id === ticket.team_id)?.name ?? "โ€”"} + {density === "comfortable" && subs.map((e) => ( +
{getSubtitleValue(e.key, ticket, {users, queues, teamsList})}
+ ))} +
+ ); + } default: return
; } @@ -1188,7 +1394,7 @@ function TicketWorkbenchContent() { name: saveViewName.trim(), filters: storedFilters, sort_key: sortKey, - columns, + columns: [...row1Fields.map((f) => ({...f, display: "column"})), ...row2EntriesResolved.map((e) => ({key: e.key, under: e.under, display: "subtitle"}))] as any, }); if (!error && data) { setSavedViewsList((prev) => [...prev, data]); @@ -1220,7 +1426,7 @@ function TicketWorkbenchContent() { name: saveViewName.trim(), filters: storedFilters, sort_key: sortKey, - columns, + columns: [...row1Fields.map((f) => ({...f, display: "column"})), ...row2EntriesResolved.map((e) => ({key: e.key, under: e.under, display: "subtitle"}))] as any, }); if (!error && data) { setSavedViewsList((prev) => [...prev, data]); @@ -1250,32 +1456,26 @@ function TicketWorkbenchContent() { > {!addFilterField ? ( <> - - + }} + >{label} + ))} +
{customFields.map((cf) => ( @@ -1295,26 +1495,55 @@ function TicketWorkbenchContent() { {addFilterField.startsWith("cf.") ? addFilterField.slice(3) : addFilterField}
- + {addFilterField === "queue" || addFilterField === "owner" ? ( + + ) : addFilterField === "created" || addFilterField === "updated" ? ( + + ) : ( + + )} {addFilterField === "queue" ? ( - + ({ value: q.id, label: q.name }))} + placeholder="Select queue..." + searchPlaceholder="Search queues..." + className="w-48" + /> ) : addFilterField === "owner" ? ( - + ({ value: u.id, label: u.username })), + ]} + placeholder="Select owner..." + searchPlaceholder="Search users..." + className="w-48" + /> + ) : addFilterField === "created" || addFilterField === "updated" ? ( + setAddFilterValue(e.target.value)} className="h-7 w-full rounded border border-input bg-card px-2 text-xs outline-none" /> ) : ( setAddFilterValue(e.target.value)} placeholder="Value" className="h-7 w-full rounded border border-input bg-card px-2 text-xs outline-none" onKeyDown={(e) => { if (e.key === "Enter" && addFilterValue.trim()) { - setFilters((prev) => [...prev, { id: crypto.randomUUID(), field: addFilterField, operator: addFilterOperator, value: addFilterValue, label: buildFilterLabel(addFilterField, addFilterOperator, addFilterValue) }]); + const field = addFilterField!; + const value = addFilterValue; + let valueLabel = field === "created" || field === "updated" ? new Date(value).toLocaleDateString() : value; + setFilters((prev) => [...prev, { id: crypto.randomUUID(), field, operator: addFilterOperator, value, label: buildFilterLabel(field, addFilterOperator, valueLabel) }]); setAddFilterField(null); setAddFilterOpen(false); } @@ -1326,11 +1555,12 @@ function TicketWorkbenchContent() { - ); - })} -
- , + { + setRow1Keys(r1.map((f) => f.key)); + setRow2Entries(r2); + }} + onClose={() => setColPickerOpen(false)} + />, document.body )}
diff --git a/web/src/app/tickets/[id]/page.tsx b/web/src/app/tickets/[id]/page.tsx index 407d8a4..54bd4bd 100644 --- a/web/src/app/tickets/[id]/page.tsx +++ b/web/src/app/tickets/[id]/page.tsx @@ -1,9 +1,9 @@ "use client"; -import { useState, useEffect, use, useCallback } from "react"; +import { useState, useEffect, use, useCallback, useRef } from "react"; import type { ReactNode } from "react"; import Link from "next/link"; -import { formatDistanceToNow } from "date-fns"; +import { formatDistanceToNow, format } from "date-fns"; import { ArrowLeftIcon, BotIcon, @@ -12,11 +12,13 @@ import { CircleIcon, Clock3Icon, FileTextIcon, + Link2Icon, MessageSquareIcon, PaperclipIcon, PencilIcon, SaveIcon, SendIcon, + Trash2Icon, UserRoundIcon, XIcon, } from "lucide-react"; @@ -32,6 +34,12 @@ import { updateTicket, updateTicketCustomField, sendComment, + uploadAttachments, + getAttachmentUrl, + getTicketLinks, + createTicketLink, + deleteTicketLink, + mergeTickets, } from "@/lib/api"; import type { Ticket, @@ -43,8 +51,12 @@ import type { QueueCustomField, PreviewResult, UpdateResult, + AttachmentUploadResult, + Attachment, + TicketLink, } from "@/lib/types"; import { Separator } from "@/components/ui/separator"; +import { SearchableSelect } from "@/components/searchable-select"; import { cn, formatTicketId } from "@/lib/utils"; const STATUS_COLORS: Record = { @@ -101,6 +113,25 @@ function userLabel(users: User[], userId: string | null) { return user?.username ?? userId; } +function formatCfValue(value: string, fieldType: string): string { + if (!value) return ""; + if (fieldType === "date") { + try { + return format(new Date(value), "MMM d, yyyy"); + } catch { + return value; + } + } + if (fieldType === "datetime") { + try { + return format(new Date(value), "MMM d, yyyy HH:mm"); + } catch { + return value; + } + } + return value; +} + function TransactionCard({ tx, users, @@ -117,6 +148,8 @@ function TransactionCard({ tx.transaction_type === "SetOwner" || tx.transaction_type === "SetTeam" || tx.transaction_type === "CustomFieldChange" || + tx.transaction_type === "LinkCreate" || + tx.transaction_type === "LinkDelete" || tx.transaction_type === "Create"; const isInternal = tx.transaction_type === "Comment"; const isMessage = tx.transaction_type === "Correspond" || isInternal; @@ -141,6 +174,26 @@ function TransactionCard({ } else if (tx.transaction_type === "CustomFieldChange") { const fieldName = tx.field ? customFieldLabels[tx.field] ?? "Custom field" : "Custom field"; message = tx.new_value ? `${fieldName} set to ${tx.new_value}` : `${fieldName} cleared`; + } else if (tx.transaction_type === "LinkCreate") { + const targetId = typeof tx.data === "object" && tx.data !== null && "target_ticket_id" in (tx.data as Record) + ? Number((tx.data as Record).target_ticket_id) + : null; + const targetSubject = typeof tx.data === "object" && tx.data !== null && "target_subject" in (tx.data as Record) + ? String((tx.data as Record).target_subject) + : ""; + const linkType = tx.field || "RelatedTo"; + const targetLabel = targetId ? `${formatTicketId(targetId)}${targetSubject ? ` (${targetSubject})` : ""}` : "?"; + message = `Linked as ${linkType} to ${targetLabel}`; + } else if (tx.transaction_type === "LinkDelete") { + const targetId = typeof tx.data === "object" && tx.data !== null && "target_ticket_id" in (tx.data as Record) + ? Number((tx.data as Record).target_ticket_id) + : null; + const targetSubject = typeof tx.data === "object" && tx.data !== null && "target_subject" in (tx.data as Record) + ? String((tx.data as Record).target_subject) + : ""; + const linkType = tx.field || "RelatedTo"; + const targetLabel = targetId ? `${formatTicketId(targetId)}${targetSubject ? ` (${targetSubject})` : ""}` : "?"; + message = `Link ${linkType} to ${targetLabel} removed`; } return ( @@ -172,10 +225,42 @@ function TransactionCard({ )} {timeAgo} + {(tx.time_worked_minutes ?? 0) > 0 && ( + + +{tx.time_worked_minutes}m + + )}

{body}

+ {tx.attachments && tx.attachments.length > 0 && ( + + )}
@@ -212,8 +297,12 @@ export default function TicketDetailPage({ const [replyText, setReplyText] = useState(""); const [replyMode, setReplyMode] = useState<"public" | "internal">("public"); + const [timeMinutes, setTimeMinutes] = useState(""); const [sending, setSending] = useState(false); const [sendError, setSendError] = useState(null); + const [pendingFiles, setPendingFiles] = useState([]); + const [uploadingFiles, setUploadingFiles] = useState(false); + const fileInputRef = useRef(null); const [statusSelectOpen, setStatusSelectOpen] = useState(false); const [pendingStatus, setPendingStatus] = useState(null); @@ -224,6 +313,16 @@ export default function TicketDetailPage({ const [editingSubject, setEditingSubject] = useState(false); const [subjectDraft, setSubjectDraft] = useState(""); const [fieldSaving, setFieldSaving] = useState<"subject" | "owner" | "team" | null>(null); + const [links, setLinks] = useState([]); + const [linkTargetId, setLinkTargetId] = useState(""); + const [linkType, setLinkType] = useState("RelatedTo"); + const [linkSaving, setLinkSaving] = useState(false); + const [linkError, setLinkError] = useState(null); + const [linkDeleting, setLinkDeleting] = useState(null); + const [mergeTargetId, setMergeTargetId] = useState(""); + const [mergeSaving, setMergeSaving] = useState(false); + const [mergeError, setMergeError] = useState(null); + const [customFieldDrafts, setCustomFieldDrafts] = useState>({}); const [customFieldSaving, setCustomFieldSaving] = useState(null); const [editingFieldId, setEditingFieldId] = useState(null); @@ -287,6 +386,14 @@ export default function TicketDetailPage({ setTeams(teamsRes.data ?? []); } + // Load ticket links + const linksRes = await getTicketLinks(id); + if (linksRes.error) { + setError((prev) => prev || linksRes.error); + } else { + setLinks(linksRes.data ?? []); + } + setLoading(false); }, [id]); @@ -466,13 +573,34 @@ export default function TicketDetailPage({ }; const handleSendComment = async () => { - if (!replyText.trim() || sending) return; + if ((!replyText.trim() && pendingFiles.length === 0) || sending) return; setSending(true); setSendError(null); + let attachmentIds: string[] = []; + + // Upload files first if any + if (pendingFiles.length > 0) { + setUploadingFiles(true); + const { data, error } = await uploadAttachments(id, pendingFiles); + setUploadingFiles(false); + + if (error) { + setSendError(error); + setSending(false); + return; + } + + if (data) { + attachmentIds = data.attachments.map((a) => a.id); + } + } + const { error } = await sendComment(id, { - body: replyText.trim(), + body: replyText.trim() || "(attached files)", internal: replyMode === "internal", + attachment_ids: attachmentIds.length > 0 ? attachmentIds : undefined, + time_worked_minutes: timeMinutes.trim() ? Number(timeMinutes.trim()) : undefined, }); setSending(false); @@ -481,12 +609,88 @@ export default function TicketDetailPage({ setSendError(error); } else { setReplyText(""); + setTimeMinutes(""); + setPendingFiles([]); setSendError(null); const txRes = await getTicketTransactions(id); if (txRes.data) setTransactions(txRes.data); } }; + const handleCreateLink = async () => { + // Accept both raw numbers and TKT-XXXX format + const raw = linkTargetId.trim().replace(/^TKT-0*/i, ''); + const targetId = Number(raw); + if (!raw || isNaN(targetId)) { + setLinkError('Enter a ticket ID (e.g. "42" or "TKT-0042")'); + return; + } + if (linkSaving) return; + + setLinkSaving(true); + setLinkError(null); + + const { data, error } = await createTicketLink(id, { + target_ticket_id: targetId, + link_type: linkType, + }); + + setLinkSaving(false); + + if (error) { + setLinkError(error); + } else { + setLinkTargetId(""); + setLinkType("RelatedTo"); + // Refresh links and transactions + const [linksRes, txRes] = await Promise.all([ + getTicketLinks(id), + getTicketTransactions(id), + ]); + if (linksRes.data) setLinks(linksRes.data); + if (txRes.data) setTransactions(txRes.data); + } + }; + + const handleDeleteLink = async (linkId: string) => { + if (linkDeleting) return; + setLinkDeleting(linkId); + + const { error } = await deleteTicketLink(id, linkId); + + setLinkDeleting(null); + + if (!error) { + const [linksRes, txRes] = await Promise.all([ + getTicketLinks(id), + getTicketTransactions(id), + ]); + if (linksRes.data) setLinks(linksRes.data); + if (txRes.data) setTransactions(txRes.data); + } + }; + + const handleMerge = async () => { + const targetId = Number(mergeTargetId.trim().replace(/^TKT-0*/i, '')); + if (!targetId || isNaN(targetId) || mergeSaving) return; + setMergeSaving(true); + setMergeError(null); + const { data, error } = await mergeTickets(id, targetId); + setMergeSaving(false); + if (error) { + setMergeError(error); + } else if (data?.ok) { + setMergeTargetId(""); + // Reload ticket and transactions + const [ticketRes, txRes] = await Promise.all([ + getTicket(id), + getTicketTransactions(id), + ]); + if (ticketRes.data) setTicket(ticketRes.data); + if (txRes.data) setTransactions(txRes.data); + } + }; + if (loading) { return (
@@ -546,10 +750,13 @@ export default function TicketDetailPage({ const currentStatusColor = STATUS_COLORS[ticket.status] || STATUS_COLORS.new; const currentStatusLabel = statusLabel(ticket.status); const customFieldLabels = Object.fromEntries( - queueFields.map((assignment) => [ - assignment.custom_field_id, - assignment.custom_field?.name ?? assignment.custom_field_id, - ]) + queueFields.flatMap((assignment) => { + const name = assignment.custom_field?.name ?? assignment.custom_field_id; + const key = assignment.custom_field?.key; + const entries: [string, string][] = [[assignment.custom_field_id, name]]; + if (key) entries.push([key, name]); + return entries; + }) ); return ( @@ -707,6 +914,28 @@ export default function TicketDetailPage({
+ {pendingFiles.length > 0 && ( +
+ {pendingFiles.map((file, i) => ( + + + {file.name} + + + ))} +
+ )} +