diff --git a/apps/backend/drizzle/postgres/0000_low_jimmy_woo.sql b/apps/backend/drizzle/postgres/0000_low_jimmy_woo.sql new file mode 100644 index 000000000..8444fde99 --- /dev/null +++ b/apps/backend/drizzle/postgres/0000_low_jimmy_woo.sql @@ -0,0 +1,277 @@ +CREATE TABLE "undb_api_token" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "user_id" text NOT NULL, + "space_id" text NOT NULL, + "token" text NOT NULL, + CONSTRAINT "undb_api_token_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "undb_attachment_mapping" ( + "attachment_id" text NOT NULL, + "table_id" text NOT NULL, + "record_id" text NOT NULL, + "field_id" text NOT NULL, + CONSTRAINT "undb_attachment_mapping_attachment_id_table_id_record_id_field_id_pk" PRIMARY KEY("attachment_id","table_id","record_id","field_id") +); +--> statement-breakpoint +CREATE TABLE "undb_attachment" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "size" integer NOT NULL, + "mime_type" text NOT NULL, + "url" text NOT NULL, + "token" text, + "created_at" bigint NOT NULL, + "created_by" text NOT NULL, + "space_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "undb_audit" ( + "id" text PRIMARY KEY NOT NULL, + "timestamp" bigint NOT NULL, + "detail" jsonb, + "meta" jsonb, + "op" text NOT NULL, + "table_id" text NOT NULL, + "record_id" text NOT NULL, + "operator_id" text NOT NULL, + "space_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "undb_base" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "space_id" text NOT NULL, + "created_at" text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, + "created_by" text NOT NULL, + "updated_at" text NOT NULL, + "updated_by" text NOT NULL, + CONSTRAINT "base_name_unique_idx" UNIQUE("name","space_id") +); +--> statement-breakpoint +CREATE TABLE "undb_dashboard_table_id_mapping" ( + "dashboard_id" text NOT NULL, + "table_id" text NOT NULL, + CONSTRAINT "undb_dashboard_table_id_mapping_dashboard_id_table_id_pk" PRIMARY KEY("dashboard_id","table_id") +); +--> statement-breakpoint +CREATE TABLE "undb_dashboard" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "description" text, + "base_id" text NOT NULL, + "space_id" text NOT NULL, + "widgets" jsonb, + "layout" jsonb, + "created_at" text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, + "created_by" text NOT NULL, + "updated_at" text NOT NULL, + "updated_by" text NOT NULL, + CONSTRAINT "dashboard_name_unique_idx" UNIQUE("name","base_id") +); +--> statement-breakpoint +CREATE TABLE "undb_email_verification_code" ( + "id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "undb_email_verification_code_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "code" text NOT NULL, + "user_id" text, + "email" text NOT NULL, + "expires_at" bigint NOT NULL, + CONSTRAINT "undb_email_verification_code_user_id_unique" UNIQUE("user_id") +); +--> statement-breakpoint +CREATE TABLE "undb_invitation" ( + "id" text PRIMARY KEY NOT NULL, + "email" text NOT NULL, + "role" text NOT NULL, + "status" text NOT NULL, + "space_id" text NOT NULL, + "invited_at" bigint NOT NULL, + "inviter_id" text NOT NULL, + CONSTRAINT "invitation_unique_idx" UNIQUE("email","space_id") +); +--> statement-breakpoint +CREATE TABLE "undb_oauth_account" ( + "provider_id" text NOT NULL, + "provider_user_id" text NOT NULL, + "user_id" text NOT NULL, + CONSTRAINT "undb_oauth_account_provider_id_provider_user_id_pk" PRIMARY KEY("provider_id","provider_user_id") +); +--> statement-breakpoint +CREATE TABLE "undb_outbox" ( + "id" text PRIMARY KEY NOT NULL, + "payload" jsonb NOT NULL, + "meta" jsonb, + "timestamp" bigint NOT NULL, + "user_id" text, + "name" text NOT NULL, + "space_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "undb_password_reset_token" ( + "id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "undb_password_reset_token_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "token" text NOT NULL, + "user_id" text NOT NULL, + "expires_at" bigint NOT NULL, + CONSTRAINT "undb_password_reset_token_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "undb_reference_id_mapping" ( + "field_id" text NOT NULL, + "table_id" text NOT NULL, + "symmetric_field_id" text, + "foreign_table_id" text NOT NULL, + CONSTRAINT "reference_id_mapping_unique_idx" UNIQUE("field_id","table_id","symmetric_field_id","foreign_table_id") +); +--> statement-breakpoint +CREATE TABLE "undb_rollup_id_mapping" ( + "field_id" text NOT NULL, + "table_id" text NOT NULL, + "rollup_id" text NOT NULL, + "rollup_table_id" text NOT NULL, + CONSTRAINT "undb_rollup_id_mapping_field_id_rollup_id_pk" PRIMARY KEY("field_id","rollup_id") +); +--> statement-breakpoint +CREATE TABLE "undb_session" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "space_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "undb_share" ( + "id" text PRIMARY KEY NOT NULL, + "target_type" text NOT NULL, + "target_id" text NOT NULL, + "enabled" boolean NOT NULL, + "space_id" text NOT NULL, + CONSTRAINT "share_unique_idx" UNIQUE("target_type","target_id") +); +--> statement-breakpoint +CREATE TABLE "undb_space" ( + "id" text PRIMARY KEY NOT NULL, + "name" text, + "is_personal" boolean NOT NULL, + "avatar" text, + "created_at" text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, + "created_by" text NOT NULL, + "updated_at" text NOT NULL, + "updated_by" text NOT NULL, + "deleted_at" bigint, + "deleted_by" text +); +--> statement-breakpoint +CREATE TABLE "undb_space_member" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "role" text NOT NULL, + "space_id" text NOT NULL, + CONSTRAINT "space_member_unique_idx" UNIQUE("user_id","space_id") +); +--> statement-breakpoint +CREATE TABLE "undb_table_id_mapping" ( + "table_id" text NOT NULL, + "subject_id" text NOT NULL, + CONSTRAINT "undb_table_id_mapping_table_id_subject_id_pk" PRIMARY KEY("table_id","subject_id") +); +--> statement-breakpoint +CREATE TABLE "undb_table" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "base_id" text NOT NULL, + "space_id" text NOT NULL, + "schema" jsonb NOT NULL, + "views" jsonb NOT NULL, + "forms" jsonb, + "rls" jsonb, + "widgets" jsonb, + "created_at" text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, + "created_by" text NOT NULL, + "updated_at" text NOT NULL, + "updated_by" text NOT NULL, + CONSTRAINT "table_name_unique_idx" UNIQUE("name","base_id") +); +--> statement-breakpoint +CREATE TABLE "undb_user" ( + "id" text PRIMARY KEY NOT NULL, + "username" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean DEFAULT false NOT NULL, + "password" text NOT NULL, + "avatar" text, + "otp_secret" text, + CONSTRAINT "undb_user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "undb_webhook" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "url" text NOT NULL, + "method" text NOT NULL, + "enabled" boolean NOT NULL, + "table_id" text NOT NULL, + "headers" jsonb NOT NULL, + "condition" jsonb, + "event" text NOT NULL, + "space_id" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "undb_api_token" ADD CONSTRAINT "undb_api_token_user_id_undb_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_api_token" ADD CONSTRAINT "undb_api_token_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_attachment_mapping" ADD CONSTRAINT "undb_attachment_mapping_attachment_id_undb_attachment_id_fk" FOREIGN KEY ("attachment_id") REFERENCES "public"."undb_attachment"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_attachment_mapping" ADD CONSTRAINT "undb_attachment_mapping_table_id_undb_table_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."undb_table"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_attachment" ADD CONSTRAINT "undb_attachment_created_by_undb_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_attachment" ADD CONSTRAINT "undb_attachment_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_audit" ADD CONSTRAINT "undb_audit_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_base" ADD CONSTRAINT "undb_base_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_base" ADD CONSTRAINT "undb_base_created_by_undb_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_base" ADD CONSTRAINT "undb_base_updated_by_undb_user_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_dashboard_table_id_mapping" ADD CONSTRAINT "undb_dashboard_table_id_mapping_dashboard_id_undb_dashboard_id_fk" FOREIGN KEY ("dashboard_id") REFERENCES "public"."undb_dashboard"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_dashboard_table_id_mapping" ADD CONSTRAINT "undb_dashboard_table_id_mapping_table_id_undb_table_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."undb_table"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_dashboard" ADD CONSTRAINT "undb_dashboard_base_id_undb_base_id_fk" FOREIGN KEY ("base_id") REFERENCES "public"."undb_base"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_dashboard" ADD CONSTRAINT "undb_dashboard_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_dashboard" ADD CONSTRAINT "undb_dashboard_created_by_undb_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_dashboard" ADD CONSTRAINT "undb_dashboard_updated_by_undb_user_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_email_verification_code" ADD CONSTRAINT "undb_email_verification_code_user_id_undb_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_invitation" ADD CONSTRAINT "undb_invitation_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_invitation" ADD CONSTRAINT "undb_invitation_inviter_id_undb_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_oauth_account" ADD CONSTRAINT "undb_oauth_account_user_id_undb_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_outbox" ADD CONSTRAINT "undb_outbox_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_password_reset_token" ADD CONSTRAINT "undb_password_reset_token_user_id_undb_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_session" ADD CONSTRAINT "undb_session_user_id_undb_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_session" ADD CONSTRAINT "undb_session_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_share" ADD CONSTRAINT "undb_share_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_space" ADD CONSTRAINT "undb_space_created_by_undb_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_space" ADD CONSTRAINT "undb_space_updated_by_undb_user_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_space" ADD CONSTRAINT "undb_space_deleted_by_undb_user_id_fk" FOREIGN KEY ("deleted_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_space_member" ADD CONSTRAINT "undb_space_member_user_id_undb_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_space_member" ADD CONSTRAINT "undb_space_member_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_table_id_mapping" ADD CONSTRAINT "undb_table_id_mapping_table_id_undb_table_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."undb_table"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_table" ADD CONSTRAINT "undb_table_base_id_undb_base_id_fk" FOREIGN KEY ("base_id") REFERENCES "public"."undb_base"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_table" ADD CONSTRAINT "undb_table_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_table" ADD CONSTRAINT "undb_table_created_by_undb_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_table" ADD CONSTRAINT "undb_table_updated_by_undb_user_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."undb_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_webhook" ADD CONSTRAINT "undb_webhook_table_id_undb_table_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."undb_table"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "undb_webhook" ADD CONSTRAINT "undb_webhook_space_id_undb_space_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."undb_space"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "api_token_space_id_idx" ON "undb_api_token" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "api_token_user_id_idx" ON "undb_api_token" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "attachment_size_idx" ON "undb_attachment" USING btree ("size");--> statement-breakpoint +CREATE INDEX "attachment_space_id_idx" ON "undb_attachment" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "audit_table_id_idx" ON "undb_audit" USING btree ("table_id");--> statement-breakpoint +CREATE INDEX "audit_space_id_idx" ON "undb_audit" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "audit_record_id_idx" ON "undb_audit" USING btree ("record_id");--> statement-breakpoint +CREATE INDEX "base_space_id_idx" ON "undb_base" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "dashboard_base_id_idx" ON "undb_dashboard" USING btree ("base_id");--> statement-breakpoint +CREATE INDEX "dashboard_space_id_idx" ON "undb_dashboard" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "invitation_space_id_idx" ON "undb_invitation" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "outbox_space_id_idx" ON "undb_outbox" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "password_reset_token_user_id_idx" ON "undb_password_reset_token" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "share_space_id_idx" ON "undb_share" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "space_name_idx" ON "undb_space" USING btree ("name");--> statement-breakpoint +CREATE INDEX "table_base_id_idx" ON "undb_table" USING btree ("base_id");--> statement-breakpoint +CREATE INDEX "table_space_id_idx" ON "undb_table" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "user_username_idx" ON "undb_user" USING btree ("username");--> statement-breakpoint +CREATE INDEX "user_email_idx" ON "undb_user" USING btree ("email");--> statement-breakpoint +CREATE INDEX "webhook_table_id_idx" ON "undb_webhook" USING btree ("table_id");--> statement-breakpoint +CREATE INDEX "webhook_space_id_idx" ON "undb_webhook" USING btree ("space_id");--> statement-breakpoint +CREATE INDEX "webhook_url_idx" ON "undb_webhook" USING btree ("url"); \ No newline at end of file diff --git a/apps/backend/drizzle/postgres/meta/0000_snapshot.json b/apps/backend/drizzle/postgres/meta/0000_snapshot.json new file mode 100644 index 000000000..33b4cc7a2 --- /dev/null +++ b/apps/backend/drizzle/postgres/meta/0000_snapshot.json @@ -0,0 +1,2130 @@ +{ + "id": "40cb0778-9031-4755-84c9-b94ad5f09718", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.undb_api_token": { + "name": "undb_api_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "api_token_space_id_idx": { + "name": "api_token_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_token_user_id_idx": { + "name": "api_token_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_api_token_user_id_undb_user_id_fk": { + "name": "undb_api_token_user_id_undb_user_id_fk", + "tableFrom": "undb_api_token", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_api_token_space_id_undb_space_id_fk": { + "name": "undb_api_token_space_id_undb_space_id_fk", + "tableFrom": "undb_api_token", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "undb_api_token_token_unique": { + "name": "undb_api_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_attachment_mapping": { + "name": "undb_attachment_mapping", + "schema": "", + "columns": { + "attachment_id": { + "name": "attachment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_attachment_mapping_attachment_id_undb_attachment_id_fk": { + "name": "undb_attachment_mapping_attachment_id_undb_attachment_id_fk", + "tableFrom": "undb_attachment_mapping", + "tableTo": "undb_attachment", + "columnsFrom": [ + "attachment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_attachment_mapping_table_id_undb_table_id_fk": { + "name": "undb_attachment_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_attachment_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_attachment_mapping_attachment_id_table_id_record_id_field_id_pk": { + "name": "undb_attachment_mapping_attachment_id_table_id_record_id_field_id_pk", + "columns": [ + "attachment_id", + "table_id", + "record_id", + "field_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_attachment": { + "name": "undb_attachment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "attachment_size_idx": { + "name": "attachment_size_idx", + "columns": [ + { + "expression": "size", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachment_space_id_idx": { + "name": "attachment_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_attachment_created_by_undb_user_id_fk": { + "name": "undb_attachment_created_by_undb_user_id_fk", + "tableFrom": "undb_attachment", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_attachment_space_id_undb_space_id_fk": { + "name": "undb_attachment_space_id_undb_space_id_fk", + "tableFrom": "undb_attachment", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_audit": { + "name": "undb_audit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "op": { + "name": "op", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "operator_id": { + "name": "operator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "audit_table_id_idx": { + "name": "audit_table_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_space_id_idx": { + "name": "audit_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_record_id_idx": { + "name": "audit_record_id_idx", + "columns": [ + { + "expression": "record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_audit_space_id_undb_space_id_fk": { + "name": "undb_audit_space_id_undb_space_id_fk", + "tableFrom": "undb_audit", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_base": { + "name": "undb_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "base_space_id_idx": { + "name": "base_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_base_space_id_undb_space_id_fk": { + "name": "undb_base_space_id_undb_space_id_fk", + "tableFrom": "undb_base", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_base_created_by_undb_user_id_fk": { + "name": "undb_base_created_by_undb_user_id_fk", + "tableFrom": "undb_base", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_base_updated_by_undb_user_id_fk": { + "name": "undb_base_updated_by_undb_user_id_fk", + "tableFrom": "undb_base", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "base_name_unique_idx": { + "name": "base_name_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "name", + "space_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_dashboard_table_id_mapping": { + "name": "undb_dashboard_table_id_mapping", + "schema": "", + "columns": { + "dashboard_id": { + "name": "dashboard_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_dashboard_table_id_mapping_dashboard_id_undb_dashboard_id_fk": { + "name": "undb_dashboard_table_id_mapping_dashboard_id_undb_dashboard_id_fk", + "tableFrom": "undb_dashboard_table_id_mapping", + "tableTo": "undb_dashboard", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_dashboard_table_id_mapping_table_id_undb_table_id_fk": { + "name": "undb_dashboard_table_id_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_dashboard_table_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_dashboard_table_id_mapping_dashboard_id_table_id_pk": { + "name": "undb_dashboard_table_id_mapping_dashboard_id_table_id_pk", + "columns": [ + "dashboard_id", + "table_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_dashboard": { + "name": "undb_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_id": { + "name": "base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "dashboard_base_id_idx": { + "name": "dashboard_base_id_idx", + "columns": [ + { + "expression": "base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dashboard_space_id_idx": { + "name": "dashboard_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_dashboard_base_id_undb_base_id_fk": { + "name": "undb_dashboard_base_id_undb_base_id_fk", + "tableFrom": "undb_dashboard", + "tableTo": "undb_base", + "columnsFrom": [ + "base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_dashboard_space_id_undb_space_id_fk": { + "name": "undb_dashboard_space_id_undb_space_id_fk", + "tableFrom": "undb_dashboard", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_dashboard_created_by_undb_user_id_fk": { + "name": "undb_dashboard_created_by_undb_user_id_fk", + "tableFrom": "undb_dashboard", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_dashboard_updated_by_undb_user_id_fk": { + "name": "undb_dashboard_updated_by_undb_user_id_fk", + "tableFrom": "undb_dashboard", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dashboard_name_unique_idx": { + "name": "dashboard_name_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "name", + "base_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_email_verification_code": { + "name": "undb_email_verification_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "undb_email_verification_code_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_email_verification_code_user_id_undb_user_id_fk": { + "name": "undb_email_verification_code_user_id_undb_user_id_fk", + "tableFrom": "undb_email_verification_code", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "undb_email_verification_code_user_id_unique": { + "name": "undb_email_verification_code_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_invitation": { + "name": "undb_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_at": { + "name": "invited_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_space_id_idx": { + "name": "invitation_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_invitation_space_id_undb_space_id_fk": { + "name": "undb_invitation_space_id_undb_space_id_fk", + "tableFrom": "undb_invitation", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_invitation_inviter_id_undb_user_id_fk": { + "name": "undb_invitation_inviter_id_undb_user_id_fk", + "tableFrom": "undb_invitation", + "tableTo": "undb_user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_unique_idx": { + "name": "invitation_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "email", + "space_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_oauth_account": { + "name": "undb_oauth_account", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_oauth_account_user_id_undb_user_id_fk": { + "name": "undb_oauth_account_user_id_undb_user_id_fk", + "tableFrom": "undb_oauth_account", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_oauth_account_provider_id_provider_user_id_pk": { + "name": "undb_oauth_account_provider_id_provider_user_id_pk", + "columns": [ + "provider_id", + "provider_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_outbox": { + "name": "undb_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "outbox_space_id_idx": { + "name": "outbox_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_outbox_space_id_undb_space_id_fk": { + "name": "undb_outbox_space_id_undb_space_id_fk", + "tableFrom": "undb_outbox", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_password_reset_token": { + "name": "undb_password_reset_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "undb_password_reset_token_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "password_reset_token_user_id_idx": { + "name": "password_reset_token_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_password_reset_token_user_id_undb_user_id_fk": { + "name": "undb_password_reset_token_user_id_undb_user_id_fk", + "tableFrom": "undb_password_reset_token", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "undb_password_reset_token_token_unique": { + "name": "undb_password_reset_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_reference_id_mapping": { + "name": "undb_reference_id_mapping", + "schema": "", + "columns": { + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "symmetric_field_id": { + "name": "symmetric_field_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "foreign_table_id": { + "name": "foreign_table_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reference_id_mapping_unique_idx": { + "name": "reference_id_mapping_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "field_id", + "table_id", + "symmetric_field_id", + "foreign_table_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_rollup_id_mapping": { + "name": "undb_rollup_id_mapping", + "schema": "", + "columns": { + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rollup_id": { + "name": "rollup_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rollup_table_id": { + "name": "rollup_table_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "undb_rollup_id_mapping_field_id_rollup_id_pk": { + "name": "undb_rollup_id_mapping_field_id_rollup_id_pk", + "columns": [ + "field_id", + "rollup_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_session": { + "name": "undb_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_session_user_id_undb_user_id_fk": { + "name": "undb_session_user_id_undb_user_id_fk", + "tableFrom": "undb_session", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_session_space_id_undb_space_id_fk": { + "name": "undb_session_space_id_undb_space_id_fk", + "tableFrom": "undb_session", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_share": { + "name": "undb_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "share_space_id_idx": { + "name": "share_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_share_space_id_undb_space_id_fk": { + "name": "undb_share_space_id_undb_space_id_fk", + "tableFrom": "undb_share", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "share_unique_idx": { + "name": "share_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "target_type", + "target_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_space": { + "name": "undb_space", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_personal": { + "name": "is_personal", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "space_name_idx": { + "name": "space_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_space_created_by_undb_user_id_fk": { + "name": "undb_space_created_by_undb_user_id_fk", + "tableFrom": "undb_space", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_space_updated_by_undb_user_id_fk": { + "name": "undb_space_updated_by_undb_user_id_fk", + "tableFrom": "undb_space", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_space_deleted_by_undb_user_id_fk": { + "name": "undb_space_deleted_by_undb_user_id_fk", + "tableFrom": "undb_space", + "tableTo": "undb_user", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_space_member": { + "name": "undb_space_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_space_member_user_id_undb_user_id_fk": { + "name": "undb_space_member_user_id_undb_user_id_fk", + "tableFrom": "undb_space_member", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_space_member_space_id_undb_space_id_fk": { + "name": "undb_space_member_space_id_undb_space_id_fk", + "tableFrom": "undb_space_member", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "space_member_unique_idx": { + "name": "space_member_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "space_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_table_id_mapping": { + "name": "undb_table_id_mapping", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "undb_table_id_mapping_table_id_undb_table_id_fk": { + "name": "undb_table_id_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_table_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_table_id_mapping_table_id_subject_id_pk": { + "name": "undb_table_id_mapping_table_id_subject_id_pk", + "columns": [ + "table_id", + "subject_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_table": { + "name": "undb_table", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_id": { + "name": "base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "forms": { + "name": "forms", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rls": { + "name": "rls", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "table_base_id_idx": { + "name": "table_base_id_idx", + "columns": [ + { + "expression": "base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_space_id_idx": { + "name": "table_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_table_base_id_undb_base_id_fk": { + "name": "undb_table_base_id_undb_base_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_base", + "columnsFrom": [ + "base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_table_space_id_undb_space_id_fk": { + "name": "undb_table_space_id_undb_space_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_table_created_by_undb_user_id_fk": { + "name": "undb_table_created_by_undb_user_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_table_updated_by_undb_user_id_fk": { + "name": "undb_table_updated_by_undb_user_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_name_unique_idx": { + "name": "table_name_unique_idx", + "nullsNotDistinct": false, + "columns": [ + "name", + "base_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_user": { + "name": "undb_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "otp_secret": { + "name": "otp_secret", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_username_idx": { + "name": "user_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_email_idx": { + "name": "user_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "undb_user_email_unique": { + "name": "undb_user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.undb_webhook": { + "name": "undb_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "condition": { + "name": "condition", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "webhook_table_id_idx": { + "name": "webhook_table_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_space_id_idx": { + "name": "webhook_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_url_idx": { + "name": "webhook_url_idx", + "columns": [ + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "undb_webhook_table_id_undb_table_id_fk": { + "name": "undb_webhook_table_id_undb_table_id_fk", + "tableFrom": "undb_webhook", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_webhook_space_id_undb_space_id_fk": { + "name": "undb_webhook_space_id_undb_space_id_fk", + "tableFrom": "undb_webhook", + "tableTo": "undb_space", + "columnsFrom": [ + "space_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/apps/backend/drizzle/postgres/meta/_journal.json b/apps/backend/drizzle/postgres/meta/_journal.json new file mode 100644 index 000000000..d1f4240c9 --- /dev/null +++ b/apps/backend/drizzle/postgres/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1736487832119, + "tag": "0000_low_jimmy_woo", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/backend/drizzle/0000_lively_warstar.sql b/apps/backend/drizzle/sqlite/0000_lively_warstar.sql similarity index 100% rename from apps/backend/drizzle/0000_lively_warstar.sql rename to apps/backend/drizzle/sqlite/0000_lively_warstar.sql diff --git a/apps/backend/drizzle/0001_familiar_joshua_kane.sql b/apps/backend/drizzle/sqlite/0001_familiar_joshua_kane.sql similarity index 100% rename from apps/backend/drizzle/0001_familiar_joshua_kane.sql rename to apps/backend/drizzle/sqlite/0001_familiar_joshua_kane.sql diff --git a/apps/backend/drizzle/0002_fixed_lockjaw.sql b/apps/backend/drizzle/sqlite/0002_fixed_lockjaw.sql similarity index 100% rename from apps/backend/drizzle/0002_fixed_lockjaw.sql rename to apps/backend/drizzle/sqlite/0002_fixed_lockjaw.sql diff --git a/apps/backend/drizzle/0003_dry_starhawk.sql b/apps/backend/drizzle/sqlite/0003_dry_starhawk.sql similarity index 100% rename from apps/backend/drizzle/0003_dry_starhawk.sql rename to apps/backend/drizzle/sqlite/0003_dry_starhawk.sql diff --git a/apps/backend/drizzle/0004_tricky_phil_sheldon.sql b/apps/backend/drizzle/sqlite/0004_tricky_phil_sheldon.sql similarity index 100% rename from apps/backend/drizzle/0004_tricky_phil_sheldon.sql rename to apps/backend/drizzle/sqlite/0004_tricky_phil_sheldon.sql diff --git a/apps/backend/drizzle/0005_narrow_khan.sql b/apps/backend/drizzle/sqlite/0005_narrow_khan.sql similarity index 100% rename from apps/backend/drizzle/0005_narrow_khan.sql rename to apps/backend/drizzle/sqlite/0005_narrow_khan.sql diff --git a/apps/backend/drizzle/0006_mature_madame_web.sql b/apps/backend/drizzle/sqlite/0006_mature_madame_web.sql similarity index 100% rename from apps/backend/drizzle/0006_mature_madame_web.sql rename to apps/backend/drizzle/sqlite/0006_mature_madame_web.sql diff --git a/apps/backend/drizzle/0007_steep_dragon_lord.sql b/apps/backend/drizzle/sqlite/0007_steep_dragon_lord.sql similarity index 100% rename from apps/backend/drizzle/0007_steep_dragon_lord.sql rename to apps/backend/drizzle/sqlite/0007_steep_dragon_lord.sql diff --git a/apps/backend/drizzle/0008_bored_terror.sql b/apps/backend/drizzle/sqlite/0008_bored_terror.sql similarity index 100% rename from apps/backend/drizzle/0008_bored_terror.sql rename to apps/backend/drizzle/sqlite/0008_bored_terror.sql diff --git a/apps/backend/drizzle/0009_workable_scorpion.sql b/apps/backend/drizzle/sqlite/0009_workable_scorpion.sql similarity index 100% rename from apps/backend/drizzle/0009_workable_scorpion.sql rename to apps/backend/drizzle/sqlite/0009_workable_scorpion.sql diff --git a/apps/backend/drizzle/0010_nostalgic_nehzno.sql b/apps/backend/drizzle/sqlite/0010_nostalgic_nehzno.sql similarity index 100% rename from apps/backend/drizzle/0010_nostalgic_nehzno.sql rename to apps/backend/drizzle/sqlite/0010_nostalgic_nehzno.sql diff --git a/apps/backend/drizzle/0011_serious_marvex.sql b/apps/backend/drizzle/sqlite/0011_serious_marvex.sql similarity index 100% rename from apps/backend/drizzle/0011_serious_marvex.sql rename to apps/backend/drizzle/sqlite/0011_serious_marvex.sql diff --git a/apps/backend/drizzle/0012_lying_tomorrow_man.sql b/apps/backend/drizzle/sqlite/0012_lying_tomorrow_man.sql similarity index 100% rename from apps/backend/drizzle/0012_lying_tomorrow_man.sql rename to apps/backend/drizzle/sqlite/0012_lying_tomorrow_man.sql diff --git a/apps/backend/drizzle/0013_lovely_mordo.sql b/apps/backend/drizzle/sqlite/0013_lovely_mordo.sql similarity index 100% rename from apps/backend/drizzle/0013_lovely_mordo.sql rename to apps/backend/drizzle/sqlite/0013_lovely_mordo.sql diff --git a/apps/backend/drizzle/0014_messy_sasquatch.sql b/apps/backend/drizzle/sqlite/0014_messy_sasquatch.sql similarity index 100% rename from apps/backend/drizzle/0014_messy_sasquatch.sql rename to apps/backend/drizzle/sqlite/0014_messy_sasquatch.sql diff --git a/apps/backend/drizzle/meta/0000_snapshot.json b/apps/backend/drizzle/sqlite/meta/0000_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0000_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0000_snapshot.json diff --git a/apps/backend/drizzle/meta/0001_snapshot.json b/apps/backend/drizzle/sqlite/meta/0001_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0001_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0001_snapshot.json diff --git a/apps/backend/drizzle/meta/0002_snapshot.json b/apps/backend/drizzle/sqlite/meta/0002_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0002_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0002_snapshot.json diff --git a/apps/backend/drizzle/meta/0003_snapshot.json b/apps/backend/drizzle/sqlite/meta/0003_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0003_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0003_snapshot.json diff --git a/apps/backend/drizzle/meta/0004_snapshot.json b/apps/backend/drizzle/sqlite/meta/0004_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0004_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0004_snapshot.json diff --git a/apps/backend/drizzle/meta/0005_snapshot.json b/apps/backend/drizzle/sqlite/meta/0005_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0005_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0005_snapshot.json diff --git a/apps/backend/drizzle/meta/0006_snapshot.json b/apps/backend/drizzle/sqlite/meta/0006_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0006_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0006_snapshot.json diff --git a/apps/backend/drizzle/meta/0007_snapshot.json b/apps/backend/drizzle/sqlite/meta/0007_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0007_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0007_snapshot.json diff --git a/apps/backend/drizzle/meta/0008_snapshot.json b/apps/backend/drizzle/sqlite/meta/0008_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0008_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0008_snapshot.json diff --git a/apps/backend/drizzle/meta/0009_snapshot.json b/apps/backend/drizzle/sqlite/meta/0009_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0009_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0009_snapshot.json diff --git a/apps/backend/drizzle/meta/0010_snapshot.json b/apps/backend/drizzle/sqlite/meta/0010_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0010_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0010_snapshot.json diff --git a/apps/backend/drizzle/meta/0011_snapshot.json b/apps/backend/drizzle/sqlite/meta/0011_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0011_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0011_snapshot.json diff --git a/apps/backend/drizzle/meta/0012_snapshot.json b/apps/backend/drizzle/sqlite/meta/0012_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0012_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0012_snapshot.json diff --git a/apps/backend/drizzle/meta/0013_snapshot.json b/apps/backend/drizzle/sqlite/meta/0013_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0013_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0013_snapshot.json diff --git a/apps/backend/drizzle/meta/0014_snapshot.json b/apps/backend/drizzle/sqlite/meta/0014_snapshot.json similarity index 100% rename from apps/backend/drizzle/meta/0014_snapshot.json rename to apps/backend/drizzle/sqlite/meta/0014_snapshot.json diff --git a/apps/backend/drizzle/meta/_journal.json b/apps/backend/drizzle/sqlite/meta/_journal.json similarity index 100% rename from apps/backend/drizzle/meta/_journal.json rename to apps/backend/drizzle/sqlite/meta/_journal.json diff --git a/apps/backend/package.json b/apps/backend/package.json index 643e736e6..c8c0b99a4 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -20,8 +20,11 @@ "@elysiajs/trpc": "^1.1.0", "@json2csv/plainjs": "^7.0.6", "@kitajs/ts-html-plugin": "latest", + "@lucia-auth/adapter-drizzle": "^1.1.0", + "@lucia-auth/adapter-postgresql": "^3.1.2", "@lucia-auth/adapter-sqlite": "^3.0.2", "@oslojs/otp": "^1.1.0", + "@types/pg": "^8.11.10", "@undb/audit": "workspace:*", "@undb/authz": "workspace:*", "@undb/base": "workspace:*", @@ -54,6 +57,7 @@ "nanoid": "^5.0.9", "nodemailer": "^6.9.16", "oslo": "^1.2.1", + "pg": "^8.13.1", "radash": "^12.1.0", "reflect-metadata": "^0.2.2", "uuid": "^11.0.5", diff --git a/apps/backend/src/modules/auth/auth.provider.ts b/apps/backend/src/modules/auth/auth.provider.ts index 1c199865b..090a4693a 100644 --- a/apps/backend/src/modules/auth/auth.provider.ts +++ b/apps/backend/src/modules/auth/auth.provider.ts @@ -1,9 +1,11 @@ +import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle" import { BunSQLiteAdapter, LibSQLAdapter } from "@lucia-auth/adapter-sqlite" import { container, inject, instanceCachingFactory } from "@undb/di" -import { env } from "@undb/env" -import { Client, DATABASE_CLIENT } from "@undb/persistence/server" +import { Client, DATABASE_CLIENT, DB_PROVIDER, pgSessionTable, pgUsers } from "@undb/persistence/server" import Database from "bun:sqlite" +import { drizzle } from "drizzle-orm/node-postgres" import { Adapter, Lucia } from "lucia" +import pg from "pg" export const LUCIA_PROVIDER = Symbol.for("LUCIA_PROVIDER") export const injectLucia = () => inject(LUCIA_PROVIDER) @@ -19,12 +21,12 @@ const createLuciaWithAdapter = (adapter: Adapter) => { }, getSessionAttributes: (attributes) => { return { - spaceId: attributes.space_id, + spaceId: attributes.space_id ?? attributes.spaceId, } }, getUserAttributes: (attributes) => { return { - emailVerified: Boolean(attributes.email_verified), + emailVerified: Boolean(attributes.email_verified || attributes.emailVerified), email: attributes.email, username: attributes.username, avatar: attributes.avatar, @@ -53,6 +55,14 @@ const createSqliteLucia = (sqlite: Database) => { return createLuciaWithAdapter(adapter) } +const createPostgresLucia = (pool: pg.Pool) => { + const db = drizzle(pool) + + const adapter = new DrizzlePostgreSQLAdapter(db, pgSessionTable, pgUsers) + + return createLuciaWithAdapter(adapter) +} + declare module "lucia" { interface Register { Lucia: ReturnType @@ -60,21 +70,28 @@ declare module "lucia" { DatabaseUserAttributes: { email: string email_verified: boolean + emailVerified: boolean username: string avatar?: string } } interface DatabaseSessionAttributes { space_id: string + spaceId: string } } container.register(LUCIA_PROVIDER, { useFactory: instanceCachingFactory((c) => { - if (env.UNDB_DB_PROVIDER === "sqlite" || !env.UNDB_DB_PROVIDER) { + const dbProvider = c.resolve(DB_PROVIDER) + if (dbProvider === "sqlite" || !dbProvider) { const sqlite = c.resolve(DATABASE_CLIENT) return createSqliteLucia(sqlite) + } else if (dbProvider === "postgres") { + const pool = c.resolve(DATABASE_CLIENT) + return createPostgresLucia(pool) } + const sqlite = c.resolve(DATABASE_CLIENT) return createTursoLucia(sqlite) }), diff --git a/apps/backend/src/modules/auth/auth.ts b/apps/backend/src/modules/auth/auth.ts index e1e485bf5..ff3e1de83 100644 --- a/apps/backend/src/modules/auth/auth.ts +++ b/apps/backend/src/modules/auth/auth.ts @@ -379,7 +379,7 @@ export class Auth { }) } - const session = await this.lucia.createSession(userId, { space_id: spaceId }) + const session = await this.lucia.createSession(userId, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) response.headers.set("Set-Cookie", sessionCookie.serialize()) @@ -434,7 +434,8 @@ export class Auth { space = Some(await this.spaceService.createSpace({ name: user.username })) await this.spaceMemberService.createMember(user.id, space.unwrap().id.value, "owner") } - const session = await this.lucia.createSession(user.id, { space_id: space.unwrap().id.value }) + const spaceId = space.unwrap().id.value + const session = await this.lucia.createSession(user.id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { @@ -548,7 +549,8 @@ export class Auth { "User should have a space", ) - const session = await this.lucia.createSession(token.user_id, { space_id: space.id.value }) + const spaceId = space.id.value + const session = await this.lucia.createSession(token.user_id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, @@ -609,7 +611,8 @@ export class Auth { .where("id", "=", user.id) .execute() - const session = await this.lucia.createSession(user.id, { space_id: validatedSession.spaceId }) + const spaceId = validatedSession.spaceId + const session = await this.lucia.createSession(user.id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, @@ -666,7 +669,7 @@ export class Auth { return redirectToSignupOrLogin("signup", email) } else { await this.spaceMemberService.createMember(user.id, spaceId, role) - const session = await this.lucia.createSession(user.id, { space_id: spaceId }) + const session = await this.lucia.createSession(user.id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return redirectToSignupOrLogin("login", email, sessionCookie) } diff --git a/apps/backend/src/modules/auth/oauth/github.ts b/apps/backend/src/modules/auth/oauth/github.ts index 8b3d234d1..aaf24eab8 100644 --- a/apps/backend/src/modules/auth/oauth/github.ts +++ b/apps/backend/src/modules/auth/oauth/github.ts @@ -88,7 +88,8 @@ export class GithubOAuth { if (existingUser) { const space = (await this.spaceService.getSpace({ userId: existingUser.user_id })).expect("space not found") - const session = await this.lucia.createSession(existingUser.user_id, { space_id: space.id.value }) + const spaceId = space.id.value + const session = await this.lucia.createSession(existingUser.user_id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, @@ -136,7 +137,8 @@ export class GithubOAuth { }) .execute() - const session = await this.lucia.createSession(existingGithubUser.id, { space_id: space.id.value }) + const spaceId = space.id.value + const session = await this.lucia.createSession(existingGithubUser.id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, @@ -182,7 +184,9 @@ export class GithubOAuth { return space }) - const session = await this.lucia.createSession(userId, { space_id: space.id.value }) + + const spaceId = space.id.value + const session = await this.lucia.createSession(userId, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, diff --git a/apps/backend/src/modules/auth/oauth/google.ts b/apps/backend/src/modules/auth/oauth/google.ts index b42df1ded..1e305f2fa 100644 --- a/apps/backend/src/modules/auth/oauth/google.ts +++ b/apps/backend/src/modules/auth/oauth/google.ts @@ -96,7 +96,9 @@ export class GoogleOAuth { if (existingUser) { const space = (await this.spaceService.getSpace({ userId: existingUser.user_id })).expect("Space not found") - const session = await this.lucia.createSession(existingUser.user_id, { space_id: space.id.value }) + + const spaceId = space.id.value + const session = await this.lucia.createSession(existingUser.user_id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, @@ -124,7 +126,8 @@ export class GoogleOAuth { }) .execute() - const session = await this.lucia.createSession(existingGoogleUser.id, { space_id: space.id.value }) + const spaceId = space.id.value + const session = await this.lucia.createSession(existingGoogleUser.id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, @@ -170,7 +173,8 @@ export class GoogleOAuth { return space }) - const session = await this.lucia.createSession(userId, { space_id: space.id.value }) + const spaceId = space.id.value + const session = await this.lucia.createSession(userId, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return new Response(null, { status: 302, diff --git a/apps/backend/src/modules/auth/otp.service.ts b/apps/backend/src/modules/auth/otp.service.ts index 53f0100b3..974033d53 100644 --- a/apps/backend/src/modules/auth/otp.service.ts +++ b/apps/backend/src/modules/auth/otp.service.ts @@ -107,7 +107,8 @@ export class OtpService implements IOtpService { await this.qb.updateTable("undb_user").set({ otp_secret: null }).where("undb_user.email", "=", email).execute() - const session = await this.lucia.createSession(id, { space_id: space.unwrap().id.value }) + const spaceId = space.unwrap().id.value + const session = await this.lucia.createSession(id, { space_id: spaceId, spaceId }) const sessionCookie = this.lucia.createSessionCookie(session.id) return sessionCookie.serialize() diff --git a/apps/backend/src/modules/space/space.module.ts b/apps/backend/src/modules/space/space.module.ts index e6fddc5f0..759edbca3 100644 --- a/apps/backend/src/modules/space/space.module.ts +++ b/apps/backend/src/modules/space/space.module.ts @@ -47,7 +47,8 @@ export class SpaceModule { }) } await this.lucia.invalidateUserSessions(user.id) - const updatedSession = await this.lucia.createSession(user.id, { space_id: space.id.value }) + const sid = space.id.value + const updatedSession = await this.lucia.createSession(user.id, { space_id: sid, spaceId: sid }) const sessionCookie = this.lucia.createSessionCookie(updatedSession.id) return new Response(null, { status: 302, @@ -74,7 +75,8 @@ export class SpaceModule { await this.lucia.invalidateSession(userId) const space = (await this.spaceService.getSpace({ userId })).expect("Space not found") - const updatedSession = await this.lucia.createSession(userId, { space_id: space.id.value }) + const sid = space.id.value + const updatedSession = await this.lucia.createSession(userId, { space_id: sid, spaceId: sid }) const sessionCookie = this.lucia.createSessionCookie(updatedSession.id) return new Response(null, { status: 200, diff --git a/apps/backend/src/registry/db.registry.ts b/apps/backend/src/registry/db.registry.ts index 9323badeb..c794431db 100644 --- a/apps/backend/src/registry/db.registry.ts +++ b/apps/backend/src/registry/db.registry.ts @@ -23,6 +23,8 @@ import { BaseQueryRepository, BaseRepository, Client, + createPostgresClient, + createPostgresQueryBuilder, createSqliteClient, createSqliteQueryBuilder, createTursoClient, @@ -32,6 +34,7 @@ import { DashboardQueryRepository, DashboardRepository, DATABASE_CLIENT, + DB_PROVIDER, InvitationQueryRepository, InvitationRepository, QUERY_BUILDER, @@ -71,31 +74,41 @@ import { USER_QUERY_REPOSITORY, USER_REPOSITORY, USER_SERVICE, UserService } fro import { WEBHOOK_QUERY_REPOSITORY, WEBHOOK_REPOSITORY } from "@undb/webhook" import Database from "bun:sqlite" import { AsyncLocalStorage } from "node:async_hooks" +import pg from "pg" const txContext = new AsyncLocalStorage() export const registerDb = () => { + container.register(DB_PROVIDER, { useValue: env.UNDB_DB_PROVIDER || "sqlite" }) container.register(CTX, { useValue: txContext }) - container.register(TX_CTX, TxContextImpl) - container.register(DATABASE_CLIENT, { useFactory: instanceCachingFactory(() => { - if (env.UNDB_DB_PROVIDER === "sqlite" || !env.UNDB_DB_PROVIDER) { + const dbProvider = container.resolve(DB_PROVIDER) + if (dbProvider === "sqlite" || !dbProvider) { return createSqliteClient("undb.sqlite") } + if (dbProvider === "postgres") { + return createPostgresClient(env.UNDB_DB_POSTGRES_URL!) + } return createTursoClient(env.UNDB_DB_TURSO_URL!, env.UNDB_DB_TURSO_AUTH_TOKEN) }), }) container.register(QUERY_BUILDER, { useFactory: instanceCachingFactory((c) => { - if (env.UNDB_DB_PROVIDER === "sqlite" || !env.UNDB_DB_PROVIDER) { + const dbProvider = container.resolve(DB_PROVIDER) + if (dbProvider === "sqlite" || !dbProvider) { const sqlite = c.resolve(DATABASE_CLIENT) return createSqliteQueryBuilder(sqlite) + } else if (dbProvider === "postgres") { + const pg = c.resolve(DATABASE_CLIENT) + return createPostgresQueryBuilder(pg) } + const sqlite = c.resolve(DATABASE_CLIENT) return createTursoQueryBuilder(sqlite) }), }) + container.register(TX_CTX, TxContextImpl) container.register(CONTEXT_TOKEN, ServerContext) container.register(SPACE_REPOSITORY, SpaceRepostitory) diff --git a/apps/frontend/src/lib/registry.svelte.ts b/apps/frontend/src/lib/registry.svelte.ts index 8e6fb8198..51078228d 100644 --- a/apps/frontend/src/lib/registry.svelte.ts +++ b/apps/frontend/src/lib/registry.svelte.ts @@ -1,4 +1,4 @@ -import { ISpaceMemberService,SPACE_MEMBER_SERVICE } from "@undb/authz" +import { ISpaceMemberService, SPACE_MEMBER_SERVICE } from "@undb/authz" import { AddDashboardWidgetCommandHandler, BulkDeleteRecordsCommandHandler, @@ -27,10 +27,11 @@ import { UpdateTableFieldCommandHandler, UpdateViewCommandHandler, } from "@undb/command-handlers" -import { CONTEXT_TOKEN,IContext } from "@undb/context" -import { CommandBus,QueryBus } from "@undb/cqrs" -import { DataService,registerDataService,TRPC_CLIENT } from "@undb/data-service" +import { CONTEXT_TOKEN, IContext } from "@undb/context" +import { CommandBus, QueryBus } from "@undb/cqrs" +import { DataService, registerDataService, TRPC_CLIENT } from "@undb/data-service" import { container } from "@undb/di" +import { DB_PROVIDER } from "@undb/persistence/client" import { GetAggregatesQueryHandler, GetBaseQueryHandler, @@ -91,6 +92,7 @@ export class Registry { async register(isLocal: boolean, isPlayground: boolean): Promise { const childContainer = container.createChildContainer() + childContainer.register(DB_PROVIDER, { useValue: "sqlite" }) childContainer.register(TRPC_CLIENT, { useValue: trpc }) await registerDataService(childContainer, isLocal, isPlayground) diff --git a/bun.lockb b/bun.lockb index 87118dee2..8b2cfdae0 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/drizzle.postgres.config.ts b/drizzle.postgres.config.ts new file mode 100644 index 000000000..94d6ce140 --- /dev/null +++ b/drizzle.postgres.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "drizzle-kit" + +export default defineConfig({ + schema: "./packages/persistence/src/schema/postgres.ts", + out: "./apps/backend/drizzle/postgres", + dialect: "postgresql", + tablesFilter: ["undb_*"], +}) diff --git a/drizzle.config.ts b/drizzle.sqlite.config.ts similarity index 66% rename from drizzle.config.ts rename to drizzle.sqlite.config.ts index d14213605..b6528b4e6 100644 --- a/drizzle.config.ts +++ b/drizzle.sqlite.config.ts @@ -1,8 +1,8 @@ import { defineConfig } from "drizzle-kit" export default defineConfig({ - schema: "./packages/persistence/src/tables.ts", - out: "./apps/backend/drizzle", + schema: "./packages/persistence/src/schema/sqlite.ts", + out: "./apps/backend/drizzle/sqlite", dialect: "sqlite", tablesFilter: ["undb_*"], dbCredentials: { diff --git a/drizzle.turso.config.ts b/drizzle.turso.config.ts index 60b2af033..a6f5d054a 100644 --- a/drizzle.turso.config.ts +++ b/drizzle.turso.config.ts @@ -1,8 +1,8 @@ import { defineConfig } from "drizzle-kit" export default defineConfig({ - schema: "./packages/persistence/src/tables.ts", - out: "./apps/backend/drizzle", + schema: "./packages/persistence/src/schema/sqlite.ts", + out: "./apps/backend/drizzle/sqlite", dialect: "turso", tablesFilter: ["undb_*"], dbCredentials: { diff --git a/package.json b/package.json index c718b8522..d8a0ae76f 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,13 @@ "dev": "bun --bun turbo dev", "lint": "turbo lint", "format": "prettier --write \"**/*.{ts,tsx,md,svelte}\"", - "studio": "drizzle-kit studio --config drizzle.config.ts", + "studio": "drizzle-kit studio --config drizzle.sqlite.config.ts", "studio:turso": "drizzle-kit studio --config drizzle.turso.config.ts", "generate": "run-s generate:db migrate:deploy", - "generate:db": "drizzle-kit generate --config drizzle.config.ts", - "migrate:db": "drizzle-kit push --config drizzle.config.ts", + "generate:db": "run-p generate:db:*", + "generate:db:postgres": "drizzle-kit generate --config drizzle.postgres.config.ts", + "generate:db:sqlite": "drizzle-kit generate --config drizzle.sqlite.config.ts", + "migrate:db": "drizzle-kit push --config drizzle.sqlite.config.ts", "move-assets": "bun run ./scripts/move-assets.ts", "migrate:deploy": "bun run ./scripts/migrate.ts", "prepare": "husky || echo 1", diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts index d9dc6d8fa..6d94b5bbc 100644 --- a/packages/env/src/index.ts +++ b/packages/env/src/index.ts @@ -36,6 +36,14 @@ const tursoEnv = createEnv({ emptyStringAsUndefined: true, }) +const postgresEnv = createEnv({ + server: { + UNDB_DB_POSTGRES_URL: z.string().optional(), + }, + runtimeEnv: import.meta.env, + emptyStringAsUndefined: true, +}) + const oauthEnv = createEnv({ server: { UNDB_OAUTH_GITHUB_ENABLED: z @@ -140,7 +148,7 @@ const emailEnv = createEnv({ const dbEnv = createEnv({ server: { - UNDB_DB_PROVIDER: z.enum(["sqlite", "turso"]).default("sqlite").optional(), + UNDB_DB_PROVIDER: z.enum(["sqlite", "turso", "postgres"]).default("sqlite").optional(), UNDB_OUTBOX_SCAN_BATCH_SIZE: z .string() .optional() @@ -163,5 +171,5 @@ export const env = createEnv({ }, runtimeEnv: import.meta.env, emptyStringAsUndefined: true, - extends: [tursoEnv, dbEnv, oauthEnv, storageEnv, s3Env, emailEnv, minioEnv, authEnv], + extends: [tursoEnv, postgresEnv, dbEnv, oauthEnv, storageEnv, s3Env, emailEnv, minioEnv, authEnv], }) diff --git a/packages/persistence/package.json b/packages/persistence/package.json index 6e5d781d1..967abb75a 100644 --- a/packages/persistence/package.json +++ b/packages/persistence/package.json @@ -13,6 +13,7 @@ "type": "module", "devDependencies": { "@types/bun": "latest", + "@types/pg": "^8.11.10", "@types/sql.js": "^1.4.9", "drizzle-kit": "^0.30.1" }, @@ -42,6 +43,8 @@ "kysely": "^0.27.5", "kysely-bun-sqlite": "^0.3.2", "kysely-wasm": "^0.7.0", + "pg": "^8.13.1", + "postgres": "^3.4.5", "radash": "^12.1.0", "reflect-metadata": "^0.2.2", "sql.js": "^1.12.0", diff --git a/packages/persistence/src/client.ts b/packages/persistence/src/client.ts index 6ff08463e..5b0e8434c 100644 --- a/packages/persistence/src/client.ts +++ b/packages/persistence/src/client.ts @@ -14,3 +14,5 @@ export * from "./record" export * from "./space" export * from "./table" export * from "./template" + +export * from "./db.provider" diff --git a/packages/persistence/src/ctx.ts b/packages/persistence/src/ctx.ts index 94bfc94f9..09138f43d 100644 --- a/packages/persistence/src/ctx.ts +++ b/packages/persistence/src/ctx.ts @@ -14,10 +14,10 @@ export const injectContext = () => inject(CTX) @singleton() export class TxContextImpl implements ITxContext { constructor( - @injectQueryBuilder() - private readonly qb: IQueryBuilder, @injectContext() private readonly context: AsyncLocalStorage, + @injectQueryBuilder() + private readonly qb: IQueryBuilder, ) {} withTransaction(callback: () => Promise): Promise { diff --git a/packages/persistence/src/db-client.ts b/packages/persistence/src/db-client.ts index 88f420ad6..0b880cd76 100644 --- a/packages/persistence/src/db-client.ts +++ b/packages/persistence/src/db-client.ts @@ -1,6 +1,7 @@ import { createClient } from "@libsql/client" import { inject } from "@undb/di" import Database from "bun:sqlite" +import pg from "pg" export const DATABASE_CLIENT = Symbol.for("DATABASE_CLIENT") @@ -13,3 +14,9 @@ export const createTursoClient = (url: string, authToken?: string) => { export const createSqliteClient = (fileName: string): Database => { return new Database(fileName) } + +export const createPostgresClient = (connectionString: string) => { + return new pg.Pool({ + connectionString, + }) +} diff --git a/packages/persistence/src/db.provider.ts b/packages/persistence/src/db.provider.ts new file mode 100644 index 000000000..1fc9733b6 --- /dev/null +++ b/packages/persistence/src/db.provider.ts @@ -0,0 +1,5 @@ +import { inject } from "@undb/di" + +export const DB_PROVIDER = Symbol.for("DB_PROVIDER") + +export const injectDbProvider = () => inject(DB_PROVIDER) diff --git a/packages/persistence/src/db.ts b/packages/persistence/src/db.ts index 12cf09dcc..ad3ef71b3 100644 --- a/packages/persistence/src/db.ts +++ b/packages/persistence/src/db.ts @@ -22,7 +22,7 @@ import type { tables, users, webhook, -} from "./tables" +} from "./schema/sqlite" type SpaceTable = Kyselify type TableTable = Kyselify diff --git a/packages/persistence/src/migrate.server.ts b/packages/persistence/src/migrate.server.ts index 06ba368da..e9b4e9b58 100644 --- a/packages/persistence/src/migrate.server.ts +++ b/packages/persistence/src/migrate.server.ts @@ -1,22 +1,35 @@ import type { Client } from "@libsql/client" import { container } from "@undb/di" -import { env } from "@undb/env" import type Database from "bun:sqlite" import { drizzle as sqliteDrizzle } from "drizzle-orm/bun-sqlite" import { migrate as sqliteMigrate } from "drizzle-orm/bun-sqlite/migrator" import { drizzle as libsqlDrizzle } from "drizzle-orm/libsql" import { migrate as libsqlMigrate } from "drizzle-orm/libsql/migrator" +import { drizzle as postgresDrizzle } from "drizzle-orm/node-postgres" +import { migrate as postgresMigrate } from "drizzle-orm/node-postgres/migrator" +import pg from "pg" import { DATABASE_CLIENT } from "./db-client" import { DrizzleLogger } from "./db.logger" +import { DB_PROVIDER } from "./db.provider" export async function dbMigrate() { - if (env.UNDB_DB_PROVIDER === "sqlite" || !env.UNDB_DB_PROVIDER) { + const dbProvider = container.resolve(DB_PROVIDER) + if (dbProvider === "sqlite" || !dbProvider) { const sqlite = container.resolve(DATABASE_CLIENT) const db = sqliteDrizzle(sqlite, { logger: new DrizzleLogger(), }) - sqliteMigrate(db, { migrationsFolder: "./drizzle" }) + sqliteMigrate(db, { migrationsFolder: "./drizzle/sqlite" }) + return + } else if (dbProvider === "postgres") { + const pg = container.resolve(DATABASE_CLIENT) + + const db = postgresDrizzle(pg, { + logger: new DrizzleLogger(), + }) + + await postgresMigrate(db, { migrationsFolder: "./drizzle/postgres" }) return } @@ -25,5 +38,5 @@ export async function dbMigrate() { logger: new DrizzleLogger(), }) - await libsqlMigrate(db, { migrationsFolder: "./drizzle" }) + await libsqlMigrate(db, { migrationsFolder: "./drizzle/sqlite" }) } diff --git a/packages/persistence/src/qb.server.ts b/packages/persistence/src/qb.server.ts index e93cf5bc5..1e361f3ef 100644 --- a/packages/persistence/src/qb.server.ts +++ b/packages/persistence/src/qb.server.ts @@ -1,7 +1,9 @@ import type { Client } from "@libsql/client" import { LibsqlDialect } from "@libsql/kysely-libsql" import { Database as SqliteDatabase } from "bun:sqlite" +import { PostgresDialect } from "kysely" import { BunSqliteDialect } from "kysely-bun-sqlite" +import pg from "pg" import { createQueryBuilderWithDialect } from "./qb.util" export function createTursoQueryBuilder(client: Client) { @@ -19,3 +21,11 @@ export function createSqliteQueryBuilder(sqlite: SqliteDatabase) { }), ) } + +export function createPostgresQueryBuilder(pg: pg.Pool) { + return createQueryBuilderWithDialect( + new PostgresDialect({ + pool: pg, + }), + ) +} diff --git a/packages/persistence/src/record/record-query-creator-visitor.ts b/packages/persistence/src/record/record-query-creator-visitor.ts index fa88f6452..80dd8531f 100644 --- a/packages/persistence/src/record/record-query-creator-visitor.ts +++ b/packages/persistence/src/record/record-query-creator-visitor.ts @@ -33,10 +33,11 @@ import type { FormulaField } from "@undb/table/src/modules/schema/fields/variant import { getTableName } from "drizzle-orm" import { sql, type QueryCreator, type SelectExpression } from "kysely" import type { IRecordQueryBuilder } from "../qb.type" -import { users } from "../tables" +import { users } from "../schema/sqlite" import { JoinTable } from "../underlying/reference/join-table" import { UnderlyingTable } from "../underlying/underlying-table" import { getRollupFn } from "../underlying/underlying-table.util" +import type { IDatabaseFnUtil } from "../utils/fn.util" import { getJsonExpandedFieldName } from "./record-utils" export class RecordQueryCreatorVisitor implements IFieldVisitor { @@ -45,6 +46,7 @@ export class RecordQueryCreatorVisitor implements IFieldVisitor { private readonly table: TableDo, private readonly foreignTables: Map, private readonly visibleFields: Field[], + private readonly dbFnUtil: IDatabaseFnUtil, ) {} #creator: QueryCreator | null = null @@ -147,14 +149,18 @@ export class RecordQueryCreatorVisitor implements IFieldVisitor { (sb) => [ `${name}.${valueField} as ${ID_TYPE}`, - visible ? sb.fn("json_group_array", [sb.ref(`${name}.${symmetricField}`)]).as(field.id.value) : undefined, + visible + ? sb.fn(this.dbFnUtil.jsonGroupArray, [sb.ref(`${name}.${symmetricField}`)]).as(field.id.value) + : undefined, // select display fields for reference ...displayFields.map((f) => - sb.fn("json_group_array", [sb.ref(`${underlyingForiegnTable.name}.${f.id.value}`)]).as(f.id.value), + sb + .fn(this.dbFnUtil.jsonGroupArray, [sb.ref(`${underlyingForiegnTable.name}.${f.id.value}`)]) + .as(f.id.value), ), ...rollupFields.map((rollupField) => sb - .fn(getRollupFn(rollupField.fn), [ + .fn(getRollupFn(rollupField.fn, this.dbFnUtil), [ sb.ref(`${underlyingForiegnTable.name}.${rollupField.rollupFieldId}`), ]) .as(rollupField.id.value), diff --git a/packages/persistence/src/record/record-query.helper.ts b/packages/persistence/src/record/record-query.helper.ts index 3fddf97bd..eafd0faa8 100644 --- a/packages/persistence/src/record/record-query.helper.ts +++ b/packages/persistence/src/record/record-query.helper.ts @@ -1,13 +1,15 @@ import { injectContext, type IContext } from "@undb/context" -import { singleton } from "@undb/di" +import { inject, singleton } from "@undb/di" import type { IPagination, Option } from "@undb/domain" import { FieldIdVo, type Field, type IViewSort, type RecordComositeSpecification, type TableDo } from "@undb/table" import { sql, type ExpressionBuilder, type SelectQueryBuilder } from "kysely" import type { ITxContext } from "../ctx.interface" import { injectTxCTX } from "../ctx.provider" +import { injectDbProvider } from "../db.provider" import { injectQueryBuilder } from "../qb.provider" import type { IRecordQueryBuilder } from "../qb.type" import { UnderlyingTable } from "../underlying/underlying-table" +import { DatabaseFnUtil, type IDatabaseFnUtil } from "../utils/fn.util" import { RecordQueryCreatorVisitor } from "./record-query-creator-visitor" import { RecordQuerySpecCreatorVisitor } from "./record-query-spec-creator-visitor" import { RecordReferenceVisitor } from "./record-reference-visitor" @@ -24,6 +26,10 @@ export class RecordQueryHelper { private readonly context: IContext, @injectTxCTX() private readonly txContext: ITxContext, + @injectDbProvider() + private readonly dbProvider: string, + @inject(DatabaseFnUtil) + private readonly dbFnUtil: IDatabaseFnUtil, ) {} createQueryCreator( @@ -34,7 +40,7 @@ export class RecordQueryHelper { ) { const trx = this.txContext.getAnonymousTransaction() - let qb = new RecordQueryCreatorVisitor(trx, table, foreignTables, visibleFields).create() + let qb = new RecordQueryCreatorVisitor(trx, table, foreignTables, visibleFields, this.dbFnUtil).create() const visitor = new RecordQuerySpecCreatorVisitor(trx, qb, table) if (spec.isSome()) { spec.unwrap().accept(visitor) @@ -49,6 +55,7 @@ export class RecordQueryHelper { foreignTables: Map, visibleFields: Field[], spec: Option, + aggregate = false, ) { const t = new UnderlyingTable(table) const qb = this.createQueryCreator(table, foreignTables, visibleFields, spec) @@ -57,7 +64,11 @@ export class RecordQueryHelper { .selectFrom(table.id.value) .$call((qb) => new RecordReferenceVisitor(qb, table).join(visibleFields)) .$if(spec.isSome(), (qb) => new RecordSpecReferenceVisitor(qb, table).$join(spec.unwrap())) - .select((sb) => new RecordSelectFieldVisitor(t, foreignTables, sb).$select(visibleFields)) + .$if(!aggregate, (qb) => + qb.select((sb) => + new RecordSelectFieldVisitor(t, foreignTables, sb, this.dbProvider, this.dbFnUtil).$select(visibleFields), + ), + ) } handleWhere(table: TableDo, spec: Option) { diff --git a/packages/persistence/src/record/record-select-field-visitor.ts b/packages/persistence/src/record/record-select-field-visitor.ts index 44b4a2dcb..25f6d086c 100644 --- a/packages/persistence/src/record/record-select-field-visitor.ts +++ b/packages/persistence/src/record/record-select-field-visitor.ts @@ -32,9 +32,10 @@ import { import type { FormulaField } from "@undb/table/src/modules/schema/fields/variants/formula-field" import { getTableName } from "drizzle-orm" import { sql, type ExpressionBuilder, type SelectExpression } from "kysely" -import { users } from "../tables" +import { users } from "../schema/sqlite" import type { UnderlyingTable } from "../underlying/underlying-table" import { getDateRangeFieldName } from "../underlying/underlying-table.util" +import type { IDatabaseFnUtil } from "../utils/fn.util" import { createDisplayFieldName } from "./record-utils" export class RecordSelectFieldVisitor implements IFieldVisitor { @@ -59,11 +60,15 @@ export class RecordSelectFieldVisitor implements IFieldVisitor { private readonly table: UnderlyingTable, private readonly foreignTables: Map, private readonly eb: ExpressionBuilder, + private readonly dbProvider: string, + private readonly dbFnUtil: IDatabaseFnUtil, ) { this.#addSelect(this.getField(ID_TYPE)) } #selectSingelUser(field: UserField | CreatedByField | UpdatedByField) { + const db = this.dbProvider + const as = createDisplayFieldName(field) const user = getTableName(users) @@ -71,7 +76,7 @@ export class RecordSelectFieldVisitor implements IFieldVisitor { .selectFrom(user) .select( this.eb - .fn("json_object", [ + .fn(this.dbFnUtil.jsonObject, [ sql.raw("'username'"), this.eb.fn.coalesce(`${user}.${users.username.name}`, sql`NULL`), sql.raw("'email'"), @@ -122,8 +127,7 @@ export class RecordSelectFieldVisitor implements IFieldVisitor { } button(field: ButtonField): void {} currency(field: CurrencyField): void { - const fieldName = this.getField(field.id.value) - const selection = sql`${sql.raw(fieldName)} / 100.0`.as(field.id.value) + const selection = sql`${sql.raw(`${this.table.name}."${field.id.value}"`)} / 100.0`.as(field.id.value) this.#addSelect(selection) } rating(field: RatingField): void { @@ -155,7 +159,7 @@ export class RecordSelectFieldVisitor implements IFieldVisitor { const displayFields = foreignTable.schema.getDisplayFields() const select = this.eb .fn( - "json_object", + this.dbFnUtil.jsonObject, displayFields.flatMap((displayField) => [ sql.raw(`'${displayField.id.value}'`), `${field.id.value}.${displayField.id.value}`, @@ -181,7 +185,7 @@ export class RecordSelectFieldVisitor implements IFieldVisitor { } dateRange(field: DateRangeField): void { const { start, end } = getDateRangeFieldName(field) - this.#addSelect(this.eb.fn("json_array", [this.getField(start), this.getField(end)]).as(field.id.value)) + this.#addSelect(this.eb.fn(this.dbFnUtil.jsonArray, [this.getField(start), this.getField(end)]).as(field.id.value)) } checkbox(field: CheckboxField): void { this.#addSelect(this.getField(field.id.value)) @@ -199,8 +203,8 @@ export class RecordSelectFieldVisitor implements IFieldVisitor { .when(`${this.table.name}.${field.id.value}`, "is", null) .then(null) .else( - this.eb.fn("json_group_array", [ - this.eb.fn("json_object", [ + this.eb.fn(this.dbFnUtil.jsonGroupArray, [ + this.eb.fn(this.dbFnUtil.jsonObject, [ sql.raw("'username'"), this.eb.fn.coalesce(`${field.id.value}.${users.username.name}`, sql`NULL`), sql.raw("'email'"), diff --git a/packages/persistence/src/record/record-utils.ts b/packages/persistence/src/record/record-utils.ts index 1c7bc85a1..2bb233dcd 100644 --- a/packages/persistence/src/record/record-utils.ts +++ b/packages/persistence/src/record/record-utils.ts @@ -176,6 +176,11 @@ export function getRecordDTOFromEntity(table: TableDo, entity: any, foreignTable } } + if (field.type === "currency") { + values[key] = Number(value) + continue + } + if (field.type === "checkbox") { values[key] = Boolean(value) continue @@ -191,6 +196,11 @@ export function getRecordDTOFromEntity(table: TableDo, entity: any, foreignTable values[key] = [start?.toISOString() ?? null, end?.toISOString() ?? null] continue } + if (field.type === "autoIncrement") { + values[key] = Number(value) + continue + } + values[key] = value } } diff --git a/packages/persistence/src/record/record.mutate-visitor.ts b/packages/persistence/src/record/record.mutate-visitor.ts index d413639ce..832f32654 100644 --- a/packages/persistence/src/record/record.mutate-visitor.ts +++ b/packages/persistence/src/record/record.mutate-visitor.ts @@ -90,19 +90,24 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR private readonly qb: IRecordQueryBuilder, private readonly eb: ExpressionBuilder, private readonly context: IContext, + private readonly dbProvider: string, ) { super() } + #setDate(fieldId: string, value: Date | null) { + if (value) { + this.setData(fieldId, this.dbProvider === "postgres" ? value : value.getTime()) + } else { + this.setData(fieldId, null) + } + } + idIn(spec: IdIn): void { throw new Error("Method not implemented.") } checkboxEqual(spec: CheckboxEqual): void { - if (!spec.value) { - this.setData(spec.fieldId.value, false) - } else { - this.setData(spec.fieldId.value, spec.value) - } + this.setData(spec.fieldId.value, Boolean(spec.value)) } jsonEqual(spec: JsonEqual): void { if (!spec.json) { @@ -125,33 +130,33 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR dateEqual(spec: DateEqual): void { if (spec.date === "@now") { const start = startOfDay(new Date()) - this.setData(spec.fieldId.value, start.getTime()) + this.#setDate(spec.fieldId.value, start) } else if (spec.date === "@today") { const start = startOfToday() - this.setData(spec.fieldId.value, start.getTime()) + this.#setDate(spec.fieldId.value, start) } else if (spec.date === "@yesterday") { const start = startOfYesterday() - this.setData(spec.fieldId.value, start.getTime()) + this.#setDate(spec.fieldId.value, start) } else if (spec.date === "@tomorrow") { const start = startOfTomorrow() - this.setData(spec.fieldId.value, start.getTime()) + this.#setDate(spec.fieldId.value, start) } else { - this.setData(spec.fieldId.value, spec.date?.getTime() ?? null) + this.#setDate(spec.fieldId.value, spec.date) } } dateRangeEqual(spec: DateRangeEqual): void { const field = this.table.schema.getFieldById(new FieldIdVo(spec.fieldId.value)).expect("No field found") const { start, end } = getDateRangeFieldName(field as DateRangeField) - this.setData(start, spec.dateRange.start?.getTime() ?? null) - this.setData(end, spec.dateRange.end?.getTime() ?? null) + this.#setDate(start, spec.dateRange.start) + this.#setDate(end, spec.dateRange.end) } dateRangeIsEmpty(spec: DateRangeIsEmpty): void { const field = this.table.schema.getFieldById(new FieldIdVo(spec.fieldId.value)).expect("No field found") const { start, end } = getDateRangeFieldName(field as DateRangeField) - this.setData(start, null) - this.setData(end, null) + this.#setDate(start, null) + this.#setDate(end, null) } dateRangeDateIsAfter(spec: DateRangeDateIsAfter): void { throw new Error("Method not implemented.") @@ -444,7 +449,7 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR this.setData(s.fieldId.value, s.value || null) } clone(): this { - return new RecordMutateVisitor(this.table, this.record, this.qb, this.eb, this.context) as this + return new RecordMutateVisitor(this.table, this.record, this.qb, this.eb, this.context, this.dbProvider) as this } formulaEqual(s: FormulaEqual): void { throw new Error("Method not implemented.") diff --git a/packages/persistence/src/record/record.query-repository.ts b/packages/persistence/src/record/record.query-repository.ts index c5767f9ee..ba17c6082 100644 --- a/packages/persistence/src/record/record.query-repository.ts +++ b/packages/persistence/src/record/record.query-repository.ts @@ -29,28 +29,28 @@ import { getTableName } from "drizzle-orm" import { sql, type AliasedExpression, type Expression, type ExpressionBuilder } from "kysely" import { injectQueryBuilder } from "../qb.provider" import type { IRecordQueryBuilder } from "../qb.type" -import { users } from "../tables" +import { users } from "../schema/sqlite" import { UnderlyingTable } from "../underlying/underlying-table" +import { DatabaseFnUtil, type IDatabaseFnUtil } from "../utils/fn.util" import { RecordQueryHelper } from "./record-query.helper" import { RecordReferenceVisitor } from "./record-reference-visitor" import { RecordSpecReferenceVisitor } from "./record-spec-reference-visitor" import { getRecordDTOFromEntity } from "./record-utils" import { AggregateFnBuiler } from "./record.aggregate-builder" -import { RecordMapper } from "./record.mapper" @singleton() export class RecordQueryRepository implements IRecordQueryRepository { constructor( @injectQueryBuilder() private readonly qb: IRecordQueryBuilder, - @inject(RecordMapper) - private readonly mapper: RecordMapper, @injectTableRepository() private readonly tableRepo: ITableRepository, @inject(RecordQueryHelper) private readonly helper: RecordQueryHelper, @injectContext() private readonly context: IContext, + @inject(DatabaseFnUtil) + private readonly dbFnUtil: IDatabaseFnUtil, ) {} async count(tableId: TableId): Promise { @@ -67,7 +67,7 @@ export class RecordQueryRepository implements IRecordQueryRepository { const selectFields = table.getSelectFields(view, undefined) const foreignTables = await this.getForeignTables(table, selectFields) - const qb = this.helper.createQuery(table, foreignTables, selectFields, spec) + const qb = this.helper.createQuery(table, foreignTables, selectFields, spec, true) const { total } = await qb .select((eb) => eb.fn.countAll().as("total")) @@ -181,7 +181,7 @@ export class RecordQueryRepository implements IRecordQueryRepository { .selectFrom(user) .select( eb - .fn("json_object", [ + .fn(this.dbFnUtil.jsonObject, [ sql.raw("'username'"), eb.fn.coalesce(`${user}.${users.username.name}`, sql`NULL`), sql.raw("'email'"), @@ -209,9 +209,9 @@ export class RecordQueryRepository implements IRecordQueryRepository { throw new Error("value field is required") } - let valueFieldAlias = `${t.name}.${valueField.id.value}` + let valueFieldAlias = `${t.name}."${valueField.id.value}"` if (valueField.type === "currency") { - valueFieldAlias = `${t.name}.${valueField.id.value} / 100` + valueFieldAlias = `${t.name}."${valueField.id.value}" / 100` } const caseString = @@ -224,7 +224,7 @@ export class RecordQueryRepository implements IRecordQueryRepository { }) .flat() - selects.push(eb.fn("json_object", columnSelects).as("values")) + selects.push(eb.fn(this.dbFnUtil.jsonObject, columnSelects).as("values")) if (aggFn === "count") { selects.push(sql.raw(`sum(CASE WHEN "${t.name}"."${columnField.id.value}" IS NOT NULL THEN 1 END)`).as(`agg`)) diff --git a/packages/persistence/src/record/record.repository.ts b/packages/persistence/src/record/record.repository.ts index c87efe034..0757e1214 100644 --- a/packages/persistence/src/record/record.repository.ts +++ b/packages/persistence/src/record/record.repository.ts @@ -22,6 +22,7 @@ import { chunk } from "es-toolkit/array" import { sql, type CompiledQuery, type ExpressionBuilder } from "kysely" import type { ITxContext } from "../ctx.interface" import { injectTxCTX } from "../ctx.provider" +import { injectDbProvider } from "../db.provider" import { UnderlyingTable } from "../underlying/underlying-table" import { RecordQueryHelper } from "./record-query.helper" import { getRecordDTOFromEntity } from "./record-utils" @@ -43,6 +44,8 @@ export class RecordRepository implements IRecordRepository { private readonly context: IContext, @injectTxCTX() private readonly txContext: ITxContext, + @injectDbProvider() + private readonly dbProvider: string, ) {} private async getForeignTables(table: TableDo, fields: Field[]): Promise> { @@ -70,7 +73,7 @@ export class RecordRepository implements IRecordRepository { await trx .insertInto(t.name) .values((eb) => { - const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context, this.dbProvider) spec.accept(visitor) sql.push(...visitor.sql) @@ -101,7 +104,7 @@ export class RecordRepository implements IRecordRepository { .values((eb) => records.map((record) => { const spec = record.toInsertSpec(table) - const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context, this.dbProvider) spec.accept(visitor) sql.push(...visitor.sql) @@ -212,7 +215,7 @@ export class RecordRepository implements IRecordRepository { await trx .updateTable(t.name) .set((eb) => { - const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context, this.dbProvider) spec.unwrap().accept(visitor) sql.push(...visitor.sql) return { ...visitor.data, [UPDATED_BY_TYPE]: userId } @@ -247,13 +250,13 @@ export class RecordRepository implements IRecordRepository { let data = {} if (records.length) { for (const record of records) { - const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context, this.dbProvider) update.accept(visitor) queries.push(...visitor.sql) data = { ...data, ...visitor.data } } } else { - const visitor = new RecordMutateVisitor(table, null, trx, eb, this.context) + const visitor = new RecordMutateVisitor(table, null, trx, eb, this.context, this.dbProvider) update.accept(visitor) queries.push(...visitor.sql) data = visitor.data diff --git a/packages/persistence/src/schema/postgres.ts b/packages/persistence/src/schema/postgres.ts new file mode 100644 index 000000000..c899659c5 --- /dev/null +++ b/packages/persistence/src/schema/postgres.ts @@ -0,0 +1,424 @@ +import type { IAuditDetail } from "@undb/audit" +import type { IInvitationStatus, ISpaceMemberRole, ISpaceMemberWithoutOwner } from "@undb/authz" +import type { IDashboardLayouts, IDashboardWidgets } from "@undb/dashboard" +import type { RECORD_EVENTS } from "@undb/table" +import type { IWebhookMethod } from "@undb/webhook" +import { sql } from "drizzle-orm" +import { + bigint, + boolean, + index, + integer, + jsonb, + pgTableCreator, + primaryKey, + text, + timestamp, + unique, +} from "drizzle-orm/pg-core" + +const pgTable = pgTableCreator((name) => `undb_${name}`) + +export const space = pgTable( + "space", + { + id: text("id").notNull().primaryKey(), + name: text("name"), + isPersonal: boolean("is_personal").notNull(), + avatar: text("avatar"), + createdAt: text("created_at") + .notNull() + .default(sql`(CURRENT_TIMESTAMP)`), + createdBy: text("created_by") + .notNull() + .references(() => users.id), + updateAt: text("updated_at") + .notNull() + .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), + updatedBy: text("updated_by") + .notNull() + .references(() => users.id), + deletedAt: bigint("deleted_at", { mode: "bigint" }), + deletedBy: text("deleted_by").references(() => users.id), + }, + (table) => [index("space_name_idx").on(table.name)], +) + +export const tables = pgTable( + "table", + { + id: text("id").notNull().primaryKey(), + name: text("name").notNull(), + baseId: text("base_id") + .notNull() + .references(() => baseTable.id), + + spaceId: text("space_id") + .notNull() + .references(() => space.id), + + schema: jsonb("schema").notNull(), + views: jsonb("views").notNull(), + forms: jsonb("forms"), + rls: jsonb("rls"), + widgets: jsonb("widgets"), + + createdAt: text("created_at") + .notNull() + .default(sql`(CURRENT_TIMESTAMP)`), + createdBy: text("created_by") + .notNull() + .references(() => users.id), + updateAt: text("updated_at") + .notNull() + .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), + updatedBy: text("updated_by") + .notNull() + .references(() => users.id), + }, + (table) => [ + index("table_base_id_idx").on(table.baseId), + unique("table_name_unique_idx").on(table.name, table.baseId), + index("table_space_id_idx").on(table.spaceId), + ], +) + +export const tableIdMapping = pgTable( + "table_id_mapping", + { + tableId: text("table_id") + .notNull() + .references(() => tables.id), + subjectId: text("subject_id").notNull(), + }, + (table) => [primaryKey({ columns: [table.tableId, table.subjectId] })], +) + +export const referenceIdMapping = pgTable( + "reference_id_mapping", + { + fieldId: text("field_id").notNull(), + tableId: text("table_id").notNull(), + symmetricFieldId: text("symmetric_field_id"), + foreignTableId: text("foreign_table_id").notNull(), + }, + (table) => [ + unique("reference_id_mapping_unique_idx").on( + table.fieldId, + table.tableId, + table.symmetricFieldId, + table.foreignTableId, + ), + ], +) + +export const rollupIdMapping = pgTable( + "rollup_id_mapping", + { + fieldId: text("field_id").notNull(), + tableId: text("table_id").notNull(), + rollupId: text("rollup_id").notNull(), + rollupTableId: text("rollup_table_id").notNull(), + }, + (table) => [primaryKey({ columns: [table.fieldId, table.rollupId] })], +) + +export const dashboards = pgTable( + "dashboard", + { + id: text("id").notNull().primaryKey(), + name: text("name").notNull(), + description: text("description"), + baseId: text("base_id") + .notNull() + .references(() => baseTable.id), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + + widgets: jsonb("widgets").$type(), + layout: jsonb("layout").$type(), + + createdAt: text("created_at") + .notNull() + .default(sql`(CURRENT_TIMESTAMP)`), + createdBy: text("created_by") + .notNull() + .references(() => users.id), + updateAt: text("updated_at") + .notNull() + .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), + updatedBy: text("updated_by") + .notNull() + .references(() => users.id), + }, + (table) => [ + index("dashboard_base_id_idx").on(table.baseId), + unique("dashboard_name_unique_idx").on(table.name, table.baseId), + index("dashboard_space_id_idx").on(table.spaceId), + ], +) + +export const dashboardTableIdMapping = pgTable( + "dashboard_table_id_mapping", + { + dashboardId: text("dashboard_id") + .notNull() + .references(() => dashboards.id), + tableId: text("table_id") + .notNull() + .references(() => tables.id), + }, + (table) => [primaryKey({ columns: [table.dashboardId, table.tableId] })], +) + +export const attachments = pgTable( + "attachment", + { + id: text("id").notNull().primaryKey(), + name: text("name").notNull(), + size: integer("size").notNull(), + mimeType: text("mime_type").notNull(), + url: text("url").notNull(), + token: text("token"), + createdAt: bigint("created_at", { mode: "bigint" }).notNull(), + createdBy: text("created_by") + .notNull() + .references(() => users.id), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + }, + (table) => [index("attachment_size_idx").on(table.size), index("attachment_space_id_idx").on(table.spaceId)], +) + +export const attachmentMapping = pgTable( + "attachment_mapping", + { + attachmentId: text("attachment_id") + .notNull() + .references(() => attachments.id), + tableId: text("table_id") + .notNull() + .references(() => tables.id), + recordId: text("record_id").notNull(), + fieldId: text("field_id").notNull(), + }, + (table) => [primaryKey({ columns: [table.attachmentId, table.tableId, table.recordId, table.fieldId] })], +) + +export const outbox = pgTable( + "outbox", + { + id: text("id").notNull().primaryKey(), + payload: jsonb("payload").notNull(), + meta: jsonb("meta"), + timestamp: bigint("timestamp", { mode: "bigint" }).notNull(), + userId: text("user_id"), + name: text("name").notNull(), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + }, + (table) => [index("outbox_space_id_idx").on(table.spaceId)], +) + +export const users = pgTable( + "user", + { + id: text("id").notNull().primaryKey(), + username: text("username").notNull(), + email: text("email").notNull().unique(), + email_verified: boolean("email_verified").notNull().default(false), + password: text("password").notNull(), + avatar: text("avatar"), + otp_secret: text("otp_secret"), + }, + (table) => [index("user_username_idx").on(table.username), index("user_email_idx").on(table.email)], +) + +export const passwordResetTokenTable = pgTable( + "password_reset_token", + { + id: integer().primaryKey().generatedByDefaultAsIdentity(), + token: text("token").notNull().unique(), + userId: text("user_id") + .notNull() + .references(() => users.id), + expiresAt: bigint("expires_at", { mode: "bigint" }).notNull(), + }, + (table) => [index("password_reset_token_user_id_idx").on(table.userId)], +) + +export const oauthAccount = pgTable( + "oauth_account", + { + provider_id: text("provider_id").notNull(), + provider_user_id: text("provider_user_id").notNull(), + user_id: text("user_id") + .notNull() + .references(() => users.id), + }, + (table) => [primaryKey({ columns: [table.provider_id, table.provider_user_id] })], +) + +export const emailVerificationCode = pgTable("email_verification_code", { + id: integer().primaryKey().generatedByDefaultAsIdentity(), + code: text("code").notNull(), + userId: text("user_id") + .unique() + .references(() => users.id), + email: text("email").notNull(), + expires_at: bigint("expires_at", { mode: "bigint" }).notNull(), +}) + +export const sessionTable = pgTable("session", { + id: text("id").notNull().primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id), + expiresAt: timestamp("expires_at", { mode: "date", withTimezone: true }).notNull(), + spaceId: text("space_id") + .notNull() + .references(() => space.id), +}) + +export const webhook = pgTable( + "webhook", + { + id: text("id").notNull().primaryKey(), + name: text("name").notNull(), + url: text("url").notNull(), + method: text("method").notNull().$type(), + enabled: boolean("enabled").notNull(), + tableId: text("table_id") + .notNull() + .references(() => tables.id), + headers: jsonb("headers").notNull(), + condition: jsonb("condition"), + event: text("event").notNull().$type(), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + }, + (table) => [ + index("webhook_table_id_idx").on(table.tableId), + index("webhook_space_id_idx").on(table.spaceId), + index("webhook_url_idx").on(table.url), + ], +) + +export const audit = pgTable( + "audit", + { + id: text("id").notNull().primaryKey(), + timestamp: bigint("timestamp", { mode: "bigint" }).notNull(), + detail: jsonb("detail").$type(), + meta: jsonb("meta"), + op: text("op").notNull().$type(), + tableId: text("table_id").notNull(), + recordId: text("record_id").notNull(), + operatorId: text("operator_id").notNull(), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + }, + (table) => [ + index("audit_table_id_idx").on(table.tableId), + index("audit_space_id_idx").on(table.spaceId), + index("audit_record_id_idx").on(table.recordId), + ], +) + +export const spaceMember = pgTable( + "space_member", + { + id: text("id").notNull().primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id), + role: text("role").notNull().$type(), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + }, + (table) => [unique("space_member_unique_idx").on(table.userId, table.spaceId)], +) + +export const baseTable = pgTable( + "base", + { + id: text("id").notNull().primaryKey(), + name: text("name").notNull(), + spaceId: text("space_id") + .references(() => space.id) + .notNull(), + createdAt: text("created_at") + .notNull() + .default(sql`(CURRENT_TIMESTAMP)`), + createdBy: text("created_by") + .notNull() + .references(() => users.id), + updateAt: text("updated_at") + .notNull() + .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), + updatedBy: text("updated_by") + .notNull() + .references(() => users.id), + }, + (base) => [unique("base_name_unique_idx").on(base.name, base.spaceId), index("base_space_id_idx").on(base.spaceId)], +) + +export const shareTable = pgTable( + "share", + { + id: text("id").notNull().primaryKey(), + targetType: text("target_type").notNull(), + targetId: text("target_id").notNull(), + enabled: boolean("enabled").notNull(), + spaceId: text("space_id") + .notNull() + .references(() => space.id), + }, + (table) => [ + unique("share_unique_idx").on(table.targetType, table.targetId), + index("share_space_id_idx").on(table.spaceId), + ], +) + +export const invitations = pgTable( + "invitation", + { + id: text("id").notNull().primaryKey(), + email: text("email").notNull(), + role: text("role").notNull().$type(), + status: text("status").notNull().$type(), + spaceId: text("space_id") + .references(() => space.id) + .notNull(), + invitedAt: bigint("invited_at", { mode: "bigint" }).notNull(), + inviterId: text("inviter_id") + .notNull() + .references(() => users.id), + }, + (table) => [ + index("invitation_space_id_idx").on(table.spaceId), + unique("invitation_unique_idx").on(table.email, table.spaceId), + ], +) + +export const apiTokenTable = pgTable( + "api_token", + { + id: text("id").notNull().primaryKey(), + name: text("name").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id), + spaceId: text("space_id") + .references(() => space.id) + .notNull(), + token: text("token").notNull().unique(), + }, + (table) => [index("api_token_space_id_idx").on(table.spaceId), index("api_token_user_id_idx").on(table.userId)], +) diff --git a/packages/persistence/src/tables.ts b/packages/persistence/src/schema/sqlite.ts similarity index 100% rename from packages/persistence/src/tables.ts rename to packages/persistence/src/schema/sqlite.ts diff --git a/packages/persistence/src/server.ts b/packages/persistence/src/server.ts index faaf8eae8..3eb7889e1 100644 --- a/packages/persistence/src/server.ts +++ b/packages/persistence/src/server.ts @@ -9,10 +9,10 @@ export * from "./migrate.server" export * from "./qb.provider" export * from "./qb.server" export * from "./record" +export * from "./schema/sqlite" export * from "./share" export * from "./space" export * from "./table" -export * from "./tables" export * from "./template" export * from "./user" export * from "./webhook" @@ -20,6 +20,14 @@ export * from "./webhook" export { type Client } from "@libsql/client" export * from "./ctx.interface" export * from "./ctx.provider" -export { DATABASE_CLIENT, createSqliteClient, createTursoClient, injectDatabaseClient } from "./db-client" +export { + DATABASE_CLIENT, + createPostgresClient, + createSqliteClient, + createTursoClient, + injectDatabaseClient, +} from "./db-client" +export * from "./db.provider" export { injectQueryBuilder } from "./qb.provider" export { type IQueryBuilder } from "./qb.type" +export { sessionTable as pgSessionTable, users as pgUsers } from "./schema/postgres" diff --git a/packages/persistence/src/table/table.mutation-visitor.ts b/packages/persistence/src/table/table.mutation-visitor.ts index ba41ff615..401d3a071 100644 --- a/packages/persistence/src/table/table.mutation-visitor.ts +++ b/packages/persistence/src/table/table.mutation-visitor.ts @@ -38,7 +38,7 @@ import type { import { AbstractQBMutationVisitor } from "../abstract-qb.visitor" import { type IQueryBuilder } from "../qb.type" import { json } from "../qb.util" -import { tables } from "../tables" +import { tables } from "../schema/sqlite" export class TableMutationVisitor extends AbstractQBMutationVisitor implements ITableSpecVisitor { constructor( diff --git a/packages/persistence/src/template/template-data.ts b/packages/persistence/src/template/template-data.ts index 4a3507a5e..c67d4e6cd 100644 --- a/packages/persistence/src/template/template-data.ts +++ b/packages/persistence/src/template/template-data.ts @@ -1,5 +1,5 @@ import { env } from "@undb/env" -import { templates,type IBaseTemplateDTO,type ITemplateDTO } from "@undb/template" +import { templates, type IBaseTemplateDTO, type ITemplateDTO } from "@undb/template" function getTemplateImage(folder: string, file: string) { return env.UNDB_BASE_URL + "/assets/templates/" + folder + "/" + file @@ -440,16 +440,14 @@ export const templateData: ITemplateDTO[] = [ }, ] -// if (env.NODE_ENV === "development") { -// templateData.unshift({ -// id: "everything", -// icon: "💼", -// name: "Everything", -// categories: ["sales"], -// description: "A template for testing", -// template: { -// type: "base", -// template: templates.everything as IBaseTemplateDTO, -// }, -// }) -// } +templateData.unshift({ + id: "everything", + icon: "💼", + name: "Everything", + categories: ["sales"], + description: "A template for testing", + template: { + type: "base", + template: templates.everything as IBaseTemplateDTO, + }, +}) diff --git a/packages/persistence/src/underlying/conversion/strategies/string-to-user.strategy.ts b/packages/persistence/src/underlying/conversion/strategies/string-to-user.strategy.ts index 342055efe..8abc6b12a 100644 --- a/packages/persistence/src/underlying/conversion/strategies/string-to-user.strategy.ts +++ b/packages/persistence/src/underlying/conversion/strategies/string-to-user.strategy.ts @@ -1,7 +1,7 @@ import type { Field } from "@undb/table" import { getTableName } from "drizzle-orm" import { sql } from "kysely" -import { users } from "../../../tables" +import { users } from "../../../schema/sqlite" import { UnderlyingConversionStrategy } from "../conversion.interface" export class StringToUserStrategy extends UnderlyingConversionStrategy { diff --git a/packages/persistence/src/underlying/conversion/strategies/user-to-string.strategy.ts b/packages/persistence/src/underlying/conversion/strategies/user-to-string.strategy.ts index 25e2400a4..d08f40d4b 100644 --- a/packages/persistence/src/underlying/conversion/strategies/user-to-string.strategy.ts +++ b/packages/persistence/src/underlying/conversion/strategies/user-to-string.strategy.ts @@ -1,7 +1,7 @@ import { type Field } from "@undb/table" import { getTableName } from "drizzle-orm" import { CaseWhenBuilder, sql } from "kysely" -import { users } from "../../../tables" +import { users } from "../../../schema/sqlite" import { UnderlyingConversionStrategy } from "../conversion.interface" export class UserToStringStrategy extends UnderlyingConversionStrategy { diff --git a/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts index d97dc6ded..68f396094 100644 --- a/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts @@ -33,6 +33,7 @@ import type { FormulaField } from "@undb/table/src/modules/schema/fields/variant import { AlterTableBuilder, sql } from "kysely" import { AbstractQBMutationVisitor } from "../abstract-qb.visitor" import type { IRecordQueryBuilder } from "../qb.type" +import type { IDatabaseFnUtil } from "../utils/fn.util" import { getUnderlyingFormulaType } from "./underlying-formula.util" import { UnderlyingFormulaVisitor } from "./underlying-formula.visitor" import type { UnderlyingTable } from "./underlying-table" @@ -43,6 +44,7 @@ export class UnderlyingTableFieldUpdatedVisitor extends AbstractQBMutationVisito private readonly table: UnderlyingTable, private readonly prev: Field, private readonly tb: AlterTableBuilder, + private readonly dbFnUtil: IDatabaseFnUtil, ) { super() } @@ -85,16 +87,17 @@ export class UnderlyingTableFieldUpdatedVisitor extends AbstractQBMutationVisito if (deletedOptions.length === 0) { return } + const jsonGroupArray = this.dbFnUtil.jsonGroupArray const query = this.qb .updateTable(tableId) .set({ [field.id.value]: sql`( - SELECT json_group_array(value) - FROM json_each(${sql.raw(tableId + "." + field.id.value)}) + SELECT ${sql.raw(jsonGroupArray)}(value) + FROM json_each(${sql.raw(`${tableId}."${field.id.value}"`)}) WHERE value NOT IN (${sql.join(deletedOptions)}) )`, }) - .where(sql`json_array_length(${sql.raw(tableId + "." + field.id.value)})`, ">", 0) + .where(sql`json_array_length(${sql.raw(`${tableId}."${field.id.value}"`)})`, ">", 0) .compile() this.addSql(query) diff --git a/packages/persistence/src/underlying/underlying-table-field.visitor.ts b/packages/persistence/src/underlying/underlying-table-field.visitor.ts index 4b2e1ff2c..960f83585 100644 --- a/packages/persistence/src/underlying/underlying-table-field.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field.visitor.ts @@ -45,6 +45,7 @@ export class UnderlyingTableFieldVisitor private readonly qb: IQueryBuilder, private readonly t: UnderlyingTable, public tb: TB, + private readonly dbProvider: string, public readonly isNew: boolean = false, ) {} public atb: AlterTableColumnAlteringBuilder | CreateTableBuilder | null = null @@ -69,22 +70,47 @@ export class UnderlyingTableFieldVisitor const c = this.tb.addColumn(field.id.value, "timestamp", (b) => b.defaultTo(sql`(CURRENT_TIMESTAMP)`).notNull()) this.addColumn(c) - const query = sql - .raw( - ` + if (this.dbProvider === "postgres") { + const query = sql + .raw( + ` +CREATE OR REPLACE FUNCTION update_modified_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW."${field.id.value}" = now(); + RETURN NEW; +END; +$$ language 'plpgsql'; + +CREATE TRIGGER update_customer_modtime_${tableName} BEFORE UPDATE ON ${tableName} FOR EACH ROW EXECUTE PROCEDURE update_modified_column(); +`, + ) + .compile(this.qb) + + this.#sql.push(query) + } else { + const query = sql + .raw( + ` CREATE TRIGGER IF NOT EXISTS update_at_update_${tableName} AFTER UPDATE ON \`${tableName}\` BEGIN update \`${tableName}\` SET ${field.id.value} = datetime('now') WHERE ${ID_TYPE} = NEW.${ID_TYPE}; END; `, - ) - .compile(this.qb) + ) + .compile(this.qb) - this.#sql.push(query) + this.#sql.push(query) + } } autoIncrement(field: AutoIncrementField): void { - const c = this.tb.addColumn(field.id.value, "integer", (b) => b.autoIncrement().primaryKey()) - this.addColumn(c) + if (this.dbProvider === "postgres") { + const c = this.tb.addColumn(field.id.value, "bigserial", (b) => b.primaryKey()) + this.addColumn(c) + } else { + const c = this.tb.addColumn(field.id.value, "integer", (b) => b.primaryKey().autoIncrement()) + this.addColumn(c) + } } createdAt(field: CreatedAtField): void { const c = this.tb.addColumn(field.id.value, "timestamp", (b) => b.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) @@ -166,15 +192,19 @@ export class UnderlyingTableFieldVisitor const sql = this.qb.schema .createTable(joinTable.getTableName()) .ifNotExists() - .addColumn(joinTable.getSymmetricValueFieldId(), "varchar(10)", (b) => b.notNull()) - .addColumn(joinTable.getValueFieldId(), "varchar(10)", (b) => b.notNull()) - .addPrimaryKeyConstraint("primary_key", [joinTable.getSymmetricValueFieldId(), joinTable.getValueFieldId()]) + .addColumn(joinTable.getSymmetricValueFieldId(), "varchar(255)", (b) => b.notNull()) + .addColumn(joinTable.getValueFieldId(), "varchar(255)", (b) => b.notNull()) + .addPrimaryKeyConstraint(joinTable.getTableName() + "_primary_key", [ + joinTable.getSymmetricValueFieldId(), + joinTable.getValueFieldId(), + ]) .compile() this.addSql(sql) } rollup(field: RollupField): void {} checkbox(field: CheckboxField): void { - const c = this.tb.addColumn(field.id.value, "boolean", (b) => b.defaultTo(0).notNull()) + const defaultValue = this.dbProvider === "postgres" ? false : 0 + const c = this.tb.addColumn(field.id.value, "boolean", (b) => b.defaultTo(defaultValue).notNull()) this.addColumn(c) } duration(field: DurationField): void { @@ -200,6 +230,9 @@ export class UnderlyingTableFieldVisitor const type = getUnderlyingFormulaType(field.returnType) const c = this.tb.addColumn(field.id.value, type, (b) => { const column = b.generatedAlwaysAs(sql.raw(parsed)) + if (this.dbProvider === "postgres") { + return column.stored() + } return this.isNew ? column.stored() : column }) this.addColumn(c) diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 77e64331e..8269e4162 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -48,6 +48,7 @@ import type { import type { WithTableRLS } from "@undb/table/src/specifications/table-rls.specification" import { AlterTableBuilder, AlterTableColumnAlteringBuilder, CompiledQuery, CreateTableBuilder, sql } from "kysely" import type { IRecordQueryBuilder } from "../qb.type" +import type { IDatabaseFnUtil } from "../utils/fn.util" import { ConversionContext } from "./conversion/conversion.context" import { ConversionFactory } from "./conversion/conversion.factory" import { JoinTable } from "./reference/join-table" @@ -61,6 +62,8 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { public readonly table: UnderlyingTable, public readonly qb: IRecordQueryBuilder, public readonly context: IContext, + private readonly dbProvider: string, + private readonly dbFnUtil: IDatabaseFnUtil, ) { this.tb = qb.schema.alterTable(table.name) } @@ -119,7 +122,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { .updateTable(this.table.name) .where((eb) => eb.not(eb.or([eb(field.id.value, "is", null), eb(field.id.value, "=", "")]))) .set((eb) => ({ - [field.id.value]: eb.fn(`json_array`, [sql.raw(field.id.value)]), + [field.id.value]: eb.fn(this.dbFnUtil.jsonArray, [sql.raw(`${this.table.name}."${field.id.value}"`)]), })) .compile() @@ -138,7 +141,13 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { this.addSql(query) } - const fieldVisitor = new UnderlyingTableFieldUpdatedVisitor(this.qb, this.table, spec.previous, this.tb) + const fieldVisitor = new UnderlyingTableFieldUpdatedVisitor( + this.qb, + this.table, + spec.previous, + this.tb, + this.dbFnUtil, + ) spec.field.accept(fieldVisitor) this.addSql(...fieldVisitor.sql) } @@ -217,7 +226,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { withName(name: TableNameSpecification): void {} withSchema(schema: TableSchemaSpecification): void {} withNewField(schema: WithNewFieldSpecification): void { - const fieldVisitor = new UnderlyingTableFieldVisitor(this.qb, this.table, this.tb, false) + const fieldVisitor = new UnderlyingTableFieldVisitor(this.qb, this.table, this.tb, this.dbProvider, false) schema.field.accept(fieldVisitor) this.addSql(...fieldVisitor.sql) this.atb = fieldVisitor.atb diff --git a/packages/persistence/src/underlying/underlying-table.service.ts b/packages/persistence/src/underlying/underlying-table.service.ts index 8425612d8..f04fd966c 100644 --- a/packages/persistence/src/underlying/underlying-table.service.ts +++ b/packages/persistence/src/underlying/underlying-table.service.ts @@ -1,10 +1,12 @@ import { injectContext, type IContext } from "@undb/context" -import { singleton } from "@undb/di" +import { inject, singleton } from "@undb/di" import { createLogger } from "@undb/logger" import type { TableComositeSpecification, TableDo } from "@undb/table" import type { CompiledQuery } from "kysely" import type { ITxContext } from "../ctx.interface" import { injectTxCTX } from "../ctx.provider" +import { injectDbProvider } from "../db.provider" +import { DatabaseFnUtil, type IDatabaseFnUtil } from "../utils/fn.util" import { JoinTable } from "./reference/join-table" import { UnderlyingTable } from "./underlying-table" import { UnderlyingTableFieldVisitor } from "./underlying-table-field.visitor" @@ -16,6 +18,10 @@ export class UnderlyingTableService { @injectContext() private readonly context: IContext, @injectTxCTX() private readonly txContext: ITxContext, + @injectDbProvider() + private readonly dbProvider: string, + @inject(DatabaseFnUtil) + private readonly dbFnUtil: IDatabaseFnUtil, ) {} readonly logger = createLogger(UnderlyingTableService.name) @@ -28,7 +34,7 @@ export class UnderlyingTableService { .createTable(t.name) .ifNotExists() .$call((tb) => { - const visitor = new UnderlyingTableFieldVisitor(trx, t, tb, true) + const visitor = new UnderlyingTableFieldVisitor(trx, t, tb, this.dbProvider, true) for (const field of table.schema) { field.accept(visitor) } @@ -46,7 +52,7 @@ export class UnderlyingTableService { const t = new UnderlyingTable(table) const trx = this.txContext.getAnonymousTransaction() - const visitor = new UnderlyingTableSpecVisitor(t, trx, this.context) + const visitor = new UnderlyingTableSpecVisitor(t, trx, this.context, this.dbProvider, this.dbFnUtil) spec.accept(visitor) await visitor.execute() diff --git a/packages/persistence/src/underlying/underlying-table.util.ts b/packages/persistence/src/underlying/underlying-table.util.ts index 775e43690..bddc20b51 100644 --- a/packages/persistence/src/underlying/underlying-table.util.ts +++ b/packages/persistence/src/underlying/underlying-table.util.ts @@ -1,10 +1,11 @@ import type { DateRangeField, FieldType, IRollupFn } from "@undb/table" import type { ColumnDataType } from "kysely" import { match } from "ts-pattern" +import type { IDatabaseFnUtil } from "../utils/fn.util" -export function getRollupFn(fn: IRollupFn): string { +export function getRollupFn(fn: IRollupFn, dbFnUtil: IDatabaseFnUtil): string { return match(fn) - .with("lookup", () => "json_group_array") + .with("lookup", () => dbFnUtil.jsonGroupArray) .with("average", () => "avg") .with("sum", () => "sum") .with("count", () => "count") diff --git a/packages/persistence/src/utils/fn.util.ts b/packages/persistence/src/utils/fn.util.ts new file mode 100644 index 000000000..6db0a8310 --- /dev/null +++ b/packages/persistence/src/utils/fn.util.ts @@ -0,0 +1,32 @@ +import { singleton } from "@undb/di" +import { match } from "ts-pattern" +import { injectDbProvider } from "../db.provider" + +export interface IDatabaseFnUtil { + get jsonGroupArray(): string + get jsonObject(): string + get jsonArray(): string +} + +@singleton() +export class DatabaseFnUtil implements IDatabaseFnUtil { + constructor(@injectDbProvider() private readonly dbProvider: string) {} + + get jsonGroupArray() { + return match(this.dbProvider) + .with("postgres", () => "json_agg") + .otherwise(() => "json_group_array") + } + + get jsonObject() { + return match(this.dbProvider) + .with("postgres", () => "json_build_object") + .otherwise(() => "json_object") + } + + get jsonArray() { + return match(this.dbProvider) + .with("postgres", () => "json_build_array") + .otherwise(() => "json_array") + } +} diff --git a/packages/template/src/templates/everything.base.json b/packages/template/src/templates/everything.base.json index 0ced2f4b4..e96ef31b5 100644 --- a/packages/template/src/templates/everything.base.json +++ b/packages/template/src/templates/everything.base.json @@ -172,6 +172,20 @@ "option": { "fn": "{{formula_number1}} - {{formula_number2}}" } + }, + "ADD": { + "id": "formula_add", + "type": "formula", + "option": { + "fn": "ADD(1, 2)" + } + }, + "ADD Number1 Number2": { + "id": "formula_add_number1_number2", + "type": "formula", + "option": { + "fn": "ADD({{formula_number1}}, {{formula_number2}})" + } } }, "records": [ diff --git a/scripts/migrate.ts b/scripts/migrate.ts index 3751bc6da..88553425f 100644 --- a/scripts/migrate.ts +++ b/scripts/migrate.ts @@ -3,7 +3,7 @@ import fs from "node:fs" import path from "node:path" import url from "node:url" -const { default: journal } = await import("../apps/backend/drizzle/meta/_journal.json", { +const { default: journal } = await import("../apps/backend/drizzle/sqlite/meta/_journal.json", { with: { type: "json" }, }) @@ -28,7 +28,7 @@ for (let index = 0; index < journal.entries.length; index++) { console.log('(%d) Parsing migration tagged "%s"', index + 1, tag) - const filepath = path.resolve(root, "./apps/backend/drizzle", `${tag}.sql`) + const filepath = path.resolve(root, "./apps/backend/drizzle/sqlite", `${tag}.sql`) const migration_file = fs.readFileSync(filepath).toString() migrate.push({