diff --git a/apps/server/drizzle/0026_far_tusk.sql b/apps/server/drizzle/0026_far_tusk.sql new file mode 100644 index 00000000..8ceee9bd --- /dev/null +++ b/apps/server/drizzle/0026_far_tusk.sql @@ -0,0 +1,16 @@ +CREATE TYPE "public"."workflow_status" AS ENUM('Started', 'Succeeded', 'Failed', 'Error');--> statement-breakpoint +CREATE TABLE "workflows" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "user_id" text NOT NULL, + "status" "workflow_status" NOT NULL, + "message" text, + "input_parameters" jsonb NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "completed_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "workflows" ADD CONSTRAINT "workflows_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_workflows_user_id" ON "workflows" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "idx_workflows_id_user_id" ON "workflows" USING btree ("id","user_id"); \ No newline at end of file diff --git a/apps/server/drizzle/meta/0026_snapshot.json b/apps/server/drizzle/meta/0026_snapshot.json new file mode 100644 index 00000000..cf238694 --- /dev/null +++ b/apps/server/drizzle/meta/0026_snapshot.json @@ -0,0 +1,3907 @@ +{ + "id": "b131c05e-e5de-42f2-b243-f38727b06f99", + "prevId": "1840126a-72fc-4a1f-939c-fc6bfc5425f3", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.dashboard": { + "name": "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "dashboard_search_trgm_idx": { + "name": "dashboard_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset": { + "name": "dataset", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "main_run_id": { + "name": "main_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_metadata_url": { + "name": "source_metadata_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_search_trgm_idx": { + "name": "dataset_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "dataset_name_idx": { + "name": "dataset_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_created_at_idx": { + "name": "dataset_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_main_run_id_idx": { + "name": "dataset_main_run_id_idx", + "columns": [ + { + "expression": "main_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_main_run_id_dataset_run_id_fk": { + "name": "dataset_main_run_id_dataset_run_id_fk", + "tableFrom": "dataset", + "tableTo": "dataset_run", + "columnsFrom": [ + "main_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_run": { + "name": "dataset_run", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "image_code": { + "name": "image_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provenance_json": { + "name": "provenance_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provenance_url": { + "name": "provenance_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_url": { + "name": "data_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_size": { + "name": "data_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "data_etag": { + "name": "data_etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_type": { + "name": "data_type", + "type": "dataset_run_data_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "dataset_run_search_trgm_idx": { + "name": "dataset_run_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "dataset_run_dataset_idx": { + "name": "dataset_run_dataset_idx", + "columns": [ + { + "expression": "dataset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_run_created_at_idx": { + "name": "dataset_run_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_run_dataset_id_dataset_id_fk": { + "name": "dataset_run_dataset_id_dataset_id_fk", + "tableFrom": "dataset_run", + "tableTo": "dataset", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.derived_indicator": { + "name": "derived_indicator", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expression": { + "name": "expression", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "derived_indicator_search_trgm_idx": { + "name": "derived_indicator_search_trgm_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "derived_indicator_category_idx": { + "name": "derived_indicator_category_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "derived_indicator_name_idx": { + "name": "derived_indicator_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "derived_indicator_category_order_idx": { + "name": "derived_indicator_category_order_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "derived_indicator_category_id_indicator_category_id_fk": { + "name": "derived_indicator_category_id_indicator_category_id_fk", + "tableFrom": "derived_indicator", + "tableTo": "indicator_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.derived_indicator_to_indicator": { + "name": "derived_indicator_to_indicator", + "schema": "", + "columns": { + "derived_indicator_id": { + "name": "derived_indicator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "indicator_id": { + "name": "indicator_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "derived_indicator_to_indicator_derived_indicator_id_derived_indicator_id_fk": { + "name": "derived_indicator_to_indicator_derived_indicator_id_derived_indicator_id_fk", + "tableFrom": "derived_indicator_to_indicator", + "tableTo": "derived_indicator", + "columnsFrom": [ + "derived_indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "derived_indicator_to_indicator_indicator_id_indicator_id_fk": { + "name": "derived_indicator_to_indicator_indicator_id_indicator_id_fk", + "tableFrom": "derived_indicator_to_indicator", + "tableTo": "indicator", + "columnsFrom": [ + "indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "derived_indicator_to_indicator_derived_indicator_id_indicator_id_pk": { + "name": "derived_indicator_to_indicator_derived_indicator_id_indicator_id_pk", + "columns": [ + "derived_indicator_id", + "indicator_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.geometries": { + "name": "geometries", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "main_run_id": { + "name": "main_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_metadata_url": { + "name": "source_metadata_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "geometries_search_trgm_idx": { + "name": "geometries_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "geometries_name_idx": { + "name": "geometries_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "geometries_created_at_idx": { + "name": "geometries_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "geometries_main_run_id_idx": { + "name": "geometries_main_run_id_idx", + "columns": [ + { + "expression": "main_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "geometries_main_run_id_geometries_run_id_fk": { + "name": "geometries_main_run_id_geometries_run_id_fk", + "tableFrom": "geometries", + "tableTo": "geometries_run", + "columnsFrom": [ + "main_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.geometries_run": { + "name": "geometries_run", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "image_code": { + "name": "image_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provenance_json": { + "name": "provenance_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provenance_url": { + "name": "provenance_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_url": { + "name": "data_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_size": { + "name": "data_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "data_etag": { + "name": "data_etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_pmtiles_url": { + "name": "data_pmtiles_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_type": { + "name": "data_type", + "type": "geometries_run_data_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "geometries_id": { + "name": "geometries_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "geometries_run_search_trgm_idx": { + "name": "geometries_run_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "geometries_run_geometries_idx": { + "name": "geometries_run_geometries_idx", + "columns": [ + { + "expression": "geometries_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "geometries_run_created_at_idx": { + "name": "geometries_run_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "geometries_run_geometries_id_geometries_id_fk": { + "name": "geometries_run_geometries_id_geometries_id_fk", + "tableFrom": "geometries_run", + "tableTo": "geometries", + "columnsFrom": [ + "geometries_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.geometry_output": { + "name": "geometry_output", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "geometries_run_id": { + "name": "geometries_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "geometry": { + "name": "geometry", + "type": "geometry(MultiPolygon,4326)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "geometry_geometries_run_idx": { + "name": "geometry_geometries_run_idx", + "columns": [ + { + "expression": "geometries_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "geometry_output_geometries_run_id_geometries_run_id_fk": { + "name": "geometry_output_geometries_run_id_geometries_run_id_fk", + "tableFrom": "geometry_output", + "tableTo": "geometries_run", + "columnsFrom": [ + "geometries_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "geometry_name_per_run": { + "name": "geometry_name_per_run", + "nullsNotDistinct": false, + "columns": [ + "geometries_run_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.indicator": { + "name": "indicator", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "indicator_search_trgm_idx": { + "name": "indicator_search_trgm_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "indicator_category_idx": { + "name": "indicator_category_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "indicator_name_idx": { + "name": "indicator_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "indicator_category_order_idx": { + "name": "indicator_category_order_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "indicator_category_id_indicator_category_id_fk": { + "name": "indicator_category_id_indicator_category_id_fk", + "tableFrom": "indicator", + "tableTo": "indicator_category", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.indicator_category": { + "name": "indicator_category", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "indicator_category_parent_idx": { + "name": "indicator_category_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "indicator_category_name_idx": { + "name": "indicator_category_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "indicator_category_parent_order_idx": { + "name": "indicator_category_parent_order_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "indicator_category_parent_id_indicator_category_id_fk": { + "name": "indicator_category_parent_id_indicator_category_id_fk", + "tableFrom": "indicator_category", + "tableTo": "indicator_category", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product": { + "name": "product", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "main_run_id": { + "name": "main_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "geometries_id": { + "name": "geometries_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_precision": { + "name": "time_precision", + "type": "time_precision", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "product_search_trgm_idx": { + "name": "product_search_trgm_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "product_name_idx": { + "name": "product_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_dataset_id_idx": { + "name": "product_dataset_id_idx", + "columns": [ + { + "expression": "dataset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_geometries_id_idx": { + "name": "product_geometries_id_idx", + "columns": [ + { + "expression": "geometries_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_created_at_idx": { + "name": "product_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_main_run_id_idx": { + "name": "product_main_run_id_idx", + "columns": [ + { + "expression": "main_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_main_run_id_product_run_id_fk": { + "name": "product_main_run_id_product_run_id_fk", + "tableFrom": "product", + "tableTo": "product_run", + "columnsFrom": [ + "main_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_dataset_id_dataset_id_fk": { + "name": "product_dataset_id_dataset_id_fk", + "tableFrom": "product", + "tableTo": "dataset", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_geometries_id_geometries_id_fk": { + "name": "product_geometries_id_geometries_id_fk", + "tableFrom": "product", + "tableTo": "geometries", + "columnsFrom": [ + "geometries_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_output": { + "name": "product_output", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "product_run_id": { + "name": "product_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "geometry_output_id": { + "name": "geometry_output_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "indicator_id": { + "name": "indicator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_indicator_id": { + "name": "derived_indicator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_point": { + "name": "time_point", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "product_output_run_created_at_idx": { + "name": "product_output_run_created_at_idx", + "columns": [ + { + "expression": "product_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_product_run_idx": { + "name": "product_output_product_run_idx", + "columns": [ + { + "expression": "product_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_created_at_idx": { + "name": "product_output_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_geometry_output_id_idx": { + "name": "product_output_geometry_output_id_idx", + "columns": [ + { + "expression": "geometry_output_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_indicator_id_idx": { + "name": "product_output_indicator_id_idx", + "columns": [ + { + "expression": "indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_run_indicator_idx": { + "name": "product_output_run_indicator_idx", + "columns": [ + { + "expression": "product_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_output_product_run_id_product_run_id_fk": { + "name": "product_output_product_run_id_product_run_id_fk", + "tableFrom": "product_output", + "tableTo": "product_run", + "columnsFrom": [ + "product_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_output_geometry_output_id_geometry_output_id_fk": { + "name": "product_output_geometry_output_id_geometry_output_id_fk", + "tableFrom": "product_output", + "tableTo": "geometry_output", + "columnsFrom": [ + "geometry_output_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_output_indicator_id_indicator_id_fk": { + "name": "product_output_indicator_id_indicator_id_fk", + "tableFrom": "product_output", + "tableTo": "indicator", + "columnsFrom": [ + "indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_output_derived_indicator_id_derived_indicator_id_fk": { + "name": "product_output_derived_indicator_id_derived_indicator_id_fk", + "tableFrom": "product_output", + "tableTo": "derived_indicator", + "columnsFrom": [ + "derived_indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "product_output_run_indicator_time_point_geometry_output_id_unique": { + "name": "product_output_run_indicator_time_point_geometry_output_id_unique", + "nullsNotDistinct": false, + "columns": [ + "product_run_id", + "indicator_id", + "derived_indicator_id", + "time_point", + "geometry_output_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_output_dependency": { + "name": "product_output_dependency", + "schema": "", + "columns": { + "derived_product_output_id": { + "name": "derived_product_output_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dependency_product_output_id": { + "name": "dependency_product_output_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "product_output_dependency_derived_product_output_idx": { + "name": "product_output_dependency_derived_product_output_idx", + "columns": [ + { + "expression": "derived_product_output_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_dependency_dependency_product_output_idx": { + "name": "product_output_dependency_dependency_product_output_idx", + "columns": [ + { + "expression": "dependency_product_output_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_output_dependency_derived_product_output_id_product_output_id_fk": { + "name": "product_output_dependency_derived_product_output_id_product_output_id_fk", + "tableFrom": "product_output_dependency", + "tableTo": "product_output", + "columnsFrom": [ + "derived_product_output_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_output_dependency_dependency_product_output_id_product_output_id_fk": { + "name": "product_output_dependency_dependency_product_output_id_product_output_id_fk", + "tableFrom": "product_output_dependency", + "tableTo": "product_output", + "columnsFrom": [ + "dependency_product_output_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "product_output_dependency_derived_product_output_id_dependency_product_output_id_pk": { + "name": "product_output_dependency_derived_product_output_id_dependency_product_output_id_pk", + "columns": [ + "derived_product_output_id", + "dependency_product_output_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_output_summary": { + "name": "product_output_summary", + "schema": "", + "columns": { + "product_run_id": { + "name": "product_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "end_time": { + "name": "end_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "time_points": { + "name": "time_points", + "type": "timestamp[]", + "primaryKey": false, + "notNull": false + }, + "output_count": { + "name": "output_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_updated": { + "name": "last_updated", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "product_output_summary_start_time_idx": { + "name": "product_output_summary_start_time_idx", + "columns": [ + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_summary_end_time_idx": { + "name": "product_output_summary_end_time_idx", + "columns": [ + { + "expression": "end_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_output_summary_last_updated_idx": { + "name": "product_output_summary_last_updated_idx", + "columns": [ + { + "expression": "last_updated", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_output_summary_product_run_id_product_run_id_fk": { + "name": "product_output_summary_product_run_id_product_run_id_fk", + "tableFrom": "product_output_summary", + "tableTo": "product_run", + "columnsFrom": [ + "product_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_output_summary_indicator": { + "name": "product_output_summary_indicator", + "schema": "", + "columns": { + "product_run_id": { + "name": "product_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "indicator_id": { + "name": "indicator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_indicator_id": { + "name": "derived_indicator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "min_value": { + "name": "min_value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "max_value": { + "name": "max_value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "avg_value": { + "name": "avg_value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_updated": { + "name": "last_updated", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summary_indicator_product_run_idx": { + "name": "summary_indicator_product_run_idx", + "columns": [ + { + "expression": "product_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_indicator_indicator_idx": { + "name": "summary_indicator_indicator_idx", + "columns": [ + { + "expression": "indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_indicator_derived_indicator_idx": { + "name": "summary_indicator_derived_indicator_idx", + "columns": [ + { + "expression": "derived_indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_output_summary_indicator_product_run_id_product_output_summary_product_run_id_fk": { + "name": "product_output_summary_indicator_product_run_id_product_output_summary_product_run_id_fk", + "tableFrom": "product_output_summary_indicator", + "tableTo": "product_output_summary", + "columnsFrom": [ + "product_run_id" + ], + "columnsTo": [ + "product_run_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_output_summary_indicator_indicator_id_indicator_id_fk": { + "name": "product_output_summary_indicator_indicator_id_indicator_id_fk", + "tableFrom": "product_output_summary_indicator", + "tableTo": "indicator", + "columnsFrom": [ + "indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_output_summary_indicator_derived_indicator_id_derived_indicator_id_fk": { + "name": "product_output_summary_indicator_derived_indicator_id_derived_indicator_id_fk", + "tableFrom": "product_output_summary_indicator", + "tableTo": "derived_indicator", + "columnsFrom": [ + "derived_indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_indicator_pk": { + "name": "summary_indicator_pk", + "nullsNotDistinct": false, + "columns": [ + "product_run_id", + "indicator_id", + "derived_indicator_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_run": { + "name": "product_run", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "image_code": { + "name": "image_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provenance_json": { + "name": "provenance_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provenance_url": { + "name": "provenance_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_url": { + "name": "data_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_size": { + "name": "data_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "data_etag": { + "name": "data_etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_type": { + "name": "data_type", + "type": "product_run_data_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dataset_run_id": { + "name": "dataset_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "geometries_run_id": { + "name": "geometries_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "product_run_search_trgm_idx": { + "name": "product_run_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "product_run_product_created_at_idx": { + "name": "product_run_product_created_at_idx", + "columns": [ + { + "expression": "product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_run_dataset_idx": { + "name": "product_run_dataset_idx", + "columns": [ + { + "expression": "dataset_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_run_geometries_idx": { + "name": "product_run_geometries_idx", + "columns": [ + { + "expression": "geometries_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_run_created_at_idx": { + "name": "product_run_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_run_product_id_product_id_fk": { + "name": "product_run_product_id_product_id_fk", + "tableFrom": "product_run", + "tableTo": "product", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_run_dataset_run_id_dataset_run_id_fk": { + "name": "product_run_dataset_run_id_dataset_run_id_fk", + "tableFrom": "product_run", + "tableTo": "dataset_run", + "columnsFrom": [ + "dataset_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_run_geometries_run_id_geometries_run_id_fk": { + "name": "product_run_geometries_run_id_geometries_run_id_fk", + "tableFrom": "product_run", + "tableTo": "geometries_run", + "columnsFrom": [ + "geometries_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_run_assigned_derived_indicator": { + "name": "product_run_assigned_derived_indicator", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "product_run_id": { + "name": "product_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "derived_indicator_id": { + "name": "derived_indicator_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "product_run_assigned_derived_indicator_product_run_idx": { + "name": "product_run_assigned_derived_indicator_product_run_idx", + "columns": [ + { + "expression": "product_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "product_run_assigned_derived_indicator_derived_indicator_idx": { + "name": "product_run_assigned_derived_indicator_derived_indicator_idx", + "columns": [ + { + "expression": "derived_indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_run_assigned_derived_indicator_product_run_id_product_run_id_fk": { + "name": "product_run_assigned_derived_indicator_product_run_id_product_run_id_fk", + "tableFrom": "product_run_assigned_derived_indicator", + "tableTo": "product_run", + "columnsFrom": [ + "product_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_run_assigned_derived_indicator_derived_indicator_id_derived_indicator_id_fk": { + "name": "product_run_assigned_derived_indicator_derived_indicator_id_derived_indicator_id_fk", + "tableFrom": "product_run_assigned_derived_indicator", + "tableTo": "derived_indicator", + "columnsFrom": [ + "derived_indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "product_run_assigned_derived_indicator_unique": { + "name": "product_run_assigned_derived_indicator_unique", + "nullsNotDistinct": false, + "columns": [ + "product_run_id", + "derived_indicator_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assigned_derived_indicator_dep": { + "name": "assigned_derived_indicator_dep", + "schema": "", + "columns": { + "assigned_derived_indicator_id": { + "name": "assigned_derived_indicator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "indicator_id": { + "name": "indicator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_product_run_id": { + "name": "source_product_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "assigned_di_dep_assigned_idx": { + "name": "assigned_di_dep_assigned_idx", + "columns": [ + { + "expression": "assigned_derived_indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assigned_di_dep_indicator_idx": { + "name": "assigned_di_dep_indicator_idx", + "columns": [ + { + "expression": "indicator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assigned_di_dep_source_run_idx": { + "name": "assigned_di_dep_source_run_idx", + "columns": [ + { + "expression": "source_product_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assigned_derived_indicator_dep_assigned_derived_indicator_id_product_run_assigned_derived_indicator_id_fk": { + "name": "assigned_derived_indicator_dep_assigned_derived_indicator_id_product_run_assigned_derived_indicator_id_fk", + "tableFrom": "assigned_derived_indicator_dep", + "tableTo": "product_run_assigned_derived_indicator", + "columnsFrom": [ + "assigned_derived_indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "assigned_derived_indicator_dep_indicator_id_indicator_id_fk": { + "name": "assigned_derived_indicator_dep_indicator_id_indicator_id_fk", + "tableFrom": "assigned_derived_indicator_dep", + "tableTo": "indicator", + "columnsFrom": [ + "indicator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "assigned_derived_indicator_dep_source_product_run_id_product_run_id_fk": { + "name": "assigned_derived_indicator_dep_source_product_run_id_product_run_id_fk", + "tableFrom": "assigned_derived_indicator_dep", + "tableTo": "product_run", + "columnsFrom": [ + "source_product_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "assigned_derived_indicator_dep_assigned_derived_indicator_id_indicator_id_pk": { + "name": "assigned_derived_indicator_dep_assigned_derived_indicator_id_indicator_id_pk", + "columns": [ + "assigned_derived_indicator_id", + "indicator_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.report": { + "name": "report", + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "report_search_trgm_idx": { + "name": "report_search_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + }, + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflows": { + "name": "workflows", + "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 + }, + "status": { + "name": "status", + "type": "workflow_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_parameters": { + "name": "input_parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_workflows_user_id": { + "name": "idx_workflows_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflows_id_user_id": { + "name": "idx_workflows_id_user_id", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflows_user_id_user_id_fk": { + "name": "workflows_user_id_user_id_fk", + "tableFrom": "workflows", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3600000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "apikey_key_idx": { + "name": "apikey_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikey_userId_idx": { + "name": "apikey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "apikey_user_id_user_id_fk": { + "name": "apikey_user_id_user_id_fk", + "tableFrom": "apikey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "twoFactor_secret_idx": { + "name": "twoFactor_secret_idx", + "columns": [ + { + "expression": "secret", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "twoFactor_userId_idx": { + "name": "twoFactor_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "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 + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_anonymous": { + "name": "is_anonymous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.dataset_run_data_type": { + "name": "dataset_run_data_type", + "schema": "public", + "values": [ + "parquet", + "geoparquet", + "stac-geoparquet", + "zarr" + ] + }, + "public.geometries_run_data_type": { + "name": "geometries_run_data_type", + "schema": "public", + "values": [ + "geoparquet" + ] + }, + "public.product_run_data_type": { + "name": "product_run_data_type", + "schema": "public", + "values": [ + "parquet" + ] + }, + "public.time_precision": { + "name": "time_precision", + "schema": "public", + "values": [ + "hour", + "day", + "month", + "year" + ] + }, + "public.workflow_status": { + "name": "workflow_status", + "schema": "public", + "values": [ + "Started", + "Succeeded", + "Failed", + "Error" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index a77e9bb6..610a8d58 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1772420069848, "tag": "0025_hesitant_christian_walker", "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1773112296152, + "tag": "0026_far_tusk", + "breakpoints": true } ] } diff --git a/apps/server/scripts/seed.ts b/apps/server/scripts/seed.ts index e0a032c4..8345c5cb 100644 --- a/apps/server/scripts/seed.ts +++ b/apps/server/scripts/seed.ts @@ -372,6 +372,59 @@ async function main() { console.log(`Product Output ID: ${productOutput4[0]!.id}`) + // Workflows + const workflows = await db + .insert(schema.workflows) + .values([ + { + id: 'id-argo-1770871025321', + name: 'name-argo-1770871025321', + userId: 'super-admin', + status: 'Failed', + message: + 'The workflow errored because the data could not be loaded from the source URL or it was in an unsupported format.', + inputParameters: { + sourceUrl: 'https://test.com', + sourceMetadataUrl: 'https://test.com/metadata', + }, + createdAt: new Date('2026-02-12T04:37:05.36108'), + updatedAt: new Date('2026-02-12T06:12:01'), + completedAt: new Date('2026-02-12T06:12:01'), + }, + { + id: 'id-argo-1770876042773', + name: 'name-argo-1770876042773', + userId: 'super-admin', + status: 'Succeeded', + inputParameters: { + sourceUrl: 'https://abc.com', + sourceMetadataUrl: 'https://abc.com/metadata', + }, + createdAt: new Date('2026-02-12T06:00:42.773667'), + updatedAt: new Date('2026-02-12T06:05:32'), + completedAt: new Date('2026-02-12T06:05:32'), + message: null, + }, + { + id: 'id-argo-1771197137533', + name: 'name-argo-1771197137533', + userId: 'super-admin', + status: 'Started', + inputParameters: { + sourceUrl: 'https://stac-api.com', + sourceMetadataUrl: 'https://stac-api.com/metadata', + }, + createdAt: new Date('2026-02-15T23:12:17.568506'), + updatedAt: new Date('2026-02-15T23:12:17.568506'), + completedAt: null, + message: null, + }, + ]) + .onConflictDoNothing() + .returning() + + console.log(`Workflow ID: ${workflows[0]!.id}`) + console.log('Seeding end') await client.end() console.log('Connection closed') diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 063b6014..193e42d9 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -28,6 +28,7 @@ import indicator from './routes/indicator' import indicatorCategory from './routes/indicatorCategory' import report from './routes/report' import dashboard from './routes/dashboard' +import workflow from './routes/workflow' const isProduction = env.NODE_ENV === 'production' @@ -82,6 +83,7 @@ const v0ApiRoutes = app .route('/indicator-category', indicatorCategory) .route('/report', report) .route('/dashboard', dashboard) + .route('/workflow', workflow) v0ApiRoutes.openAPIRegistry.registerComponent('securitySchemes', 'ApiKeyAuth', { type: 'apiKey', diff --git a/apps/server/src/routes/workflow.ts b/apps/server/src/routes/workflow.ts new file mode 100644 index 00000000..d46ddb8d --- /dev/null +++ b/apps/server/src/routes/workflow.ts @@ -0,0 +1,429 @@ +import { createRoute, z } from '@hono/zod-openapi' +import { and, desc, eq } from 'drizzle-orm' +import { db } from '~/lib/db' +import { ServerError } from '~/lib/error' +import { + createOpenAPIApp, + createResponseSchema, + jsonErrorResponse, + validationErrorResponse, +} from '~/lib/openapi' +import { authMiddleware } from '~/middlewares/auth' +import { generateJsonResponse } from '../lib/response' +import { workflows } from '../schemas/db' +import { QueryForTable } from '../schemas/util' +import { + createWorkflowSchema, + updateWorkflowSchema, + workflowSchema, + workflowQuerySchema, +} from '@repo/schemas/crud' +import { parseQuery } from '~/utils/query' + +function durationString(ms: number | null) { + if (ms === null) return null + // Format milliseconds as human readable duration + const hours = Math.floor(ms / 3600000) + const mins = Math.floor((ms % 3600000) / 60000) + const secs = Math.floor((ms % 60000) / 1000) + let out = '' + if (hours) out += `${hours}h ` + if (hours || mins) out += `${mins}m ` + if (hours || mins || secs) out += `${secs}s ` + return out.trim() +} + +function calculateDuration( + createdAt: Date | null, + completedAt: Date | null, +): string | null { + if (!createdAt || !completedAt) return null + const duration_ms = + new Date(completedAt).getTime() - new Date(createdAt).getTime() + return durationString(duration_ms) +} + +function throwIfNotAuthenticated(userId: string | undefined) { + if (!userId) { + throw new ServerError({ + statusCode: 401, + message: 'Unauthorized', + description: 'User authentication required for this action.', + }) + } +} + +const baseWorkflowQuery = { + columns: { + id: true, + name: true, + userId: true, + status: true, + message: true, + inputParameters: true, + createdAt: true, + updatedAt: true, + completedAt: true, + }, +} satisfies QueryForTable<'workflows'> + +const workflowNotFoundError = () => + new ServerError({ + statusCode: 404, + message: 'Failed to get workflow', + description: "Workflow you're looking for is not found", + }) + +const fetchFullWorkflow = async (userId: string, id: string) => { + const record = await db.query.workflows.findFirst({ + ...baseWorkflowQuery, + where: (workflows, { eq, and }) => + and(eq(workflows.userId, userId), eq(workflows.id, id)), + }) + return record ?? null +} + +const fetchFullWorkflowOrThrow = async (userId: string, id: string) => { + const record = await fetchFullWorkflow(userId, id) + + if (!record) { + throw workflowNotFoundError() + } + + return record +} + +const app = createOpenAPIApp() + .openapi( + createRoute({ + description: 'List all workflows for a user with pagination metadata.', + method: 'get', + path: '/', + middleware: [ + authMiddleware({ + permission: 'read:workflows', + }), + ], + request: { + query: workflowQuerySchema, + }, + responses: { + 200: { + description: 'Successfully listed workflows.', + content: { + 'application/json': { + schema: createResponseSchema( + z.object({ + pageCount: z.number().int(), + totalCount: z.number().int(), + data: z.array(workflowSchema), + }), + ), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to list workflows'), + }, + }), + async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) + const { pageCount, totalCount, ...query } = await parseQuery( + workflows, + c.req.valid('query'), + { + defaultOrderBy: desc(workflows.createdAt), + searchableColumns: [workflows.name], + }, + ) + + const data = await db.query.workflows.findMany({ + ...baseWorkflowQuery, + ...query, + where: and(eq(workflows.userId, userId), query.where), + }) + + // Add duration (ms) to each workflow + const dataWithDuration = data.map((wf) => ({ + ...wf, + duration: calculateDuration(wf.createdAt, wf.completedAt), + })) + + return generateJsonResponse( + c, + { + pageCount, + data: dataWithDuration, + totalCount, + }, + 200, + ) + }, + ) + .openapi( + createRoute({ + description: 'Get a single workflow by ID.', + method: 'get', + path: '/:id', + middleware: [authMiddleware({ permission: 'read:workflows' })], + request: { + params: z.object({ id: z.string().min(1) }), + }, + responses: { + 200: { + description: 'Successfully retrieved a workflow.', + content: { + 'application/json': { + schema: createResponseSchema(workflowSchema), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 404: jsonErrorResponse('workflow not found'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to fetch workflow'), + }, + }), + async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) + const { id } = c.req.valid('param') + const record = await fetchFullWorkflowOrThrow(userId, id) + // Add duration (ms) to the workflow + const recordWithDuration = record + ? { + ...record, + duration: calculateDuration(record.createdAt, record.completedAt), + } + : null + + return generateJsonResponse(c, recordWithDuration, 200) + }, + ) + + .openapi( + createRoute({ + description: 'Create a workflow.', + method: 'post', + path: '/', + middleware: [ + authMiddleware({ + permission: 'write:workflows', + }), + ], + request: { + body: { + required: true, + content: { + 'application/json': { + schema: createWorkflowSchema, + }, + }, + }, + }, + responses: { + 201: { + description: 'Successfully created a workflow.', + content: { + 'application/json': { + schema: createResponseSchema(workflowSchema), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to create workflow'), + }, + }), + async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) + const data = c.req.valid('json') + + /* + // TODO: Should this be included in this endpoint or a seperate endpoint? It needs to be called beforehand to get the id to write to the DB. + // Function to POST to Argo Workflows API + async function submitToArgoWorkflows(input: any) { + // Replace with your Argo Workflows API endpoint + const ARGO_API_URL = + process.env.ARGO_API_URL || + 'http://argo-server.example.com/api/v1/workflows' + const response = await fetch(ARGO_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add authentication headers if needed + }, + body: JSON.stringify(input), + }) + if (!response.ok) { + throw new ServerError({ + statusCode: 500, + message: 'Failed to submit workflow to Argo', + description: await response.text(), + }) + } + const argoResult = await response.json() + // Assume argoResult contains id and name + return { + id: argoResult.metadata?.uid || argoResult.id, + name: argoResult.metadata?.name || argoResult.name, + message: 'Workflow submitted to Argo successfully.', + } + } + + // Submit workflow to Argo + let argoWorkflow + try { + argoWorkflow = await submitToArgoWorkflows(data) + } catch (err) { + throw err + } + */ + // Just for testing without Argo, we will mock the response here. Remove this when Argo integration is ready. + const argoWorkflow = { + id: `id-argo-${Date.now()}`, + name: `name-argo-${Date.now()}`, + // Leave message null on successful sumbission. It will be updated on success/failure. + // message: 'Workflow submitted to Argo successfully (mocked response).', + } + + // Insert workflow record using Argo response + const workflowRecord = { + // Override the values while developing + id: argoWorkflow.id, + name: argoWorkflow.name, + userId, + status: 'Started', + // message: argoWorkflow.message, + message: null, + // inputParameters: data, + inputParameters: { + sourceUrl: data.sourceUrl, + sourceMetadataUrl: data.sourceMetadataUrl, + }, + } + const [newWorkflow] = await db + .insert(workflows) + .values(workflowRecord) + .returning() + + if (!newWorkflow) { + throw new ServerError({ + statusCode: 500, + message: 'Failed to create workflow', + description: 'Workflow insert did not return a record', + }) + } + + const record = await fetchFullWorkflowOrThrow( + newWorkflow.userId, + newWorkflow.id, + ) + + return generateJsonResponse(c, record, 201, 'Workflow created') + }, + ) + + .openapi( + createRoute({ + description: 'Update a workflow.', + method: 'patch', + path: '/:id', + middleware: [ + authMiddleware({ + permission: 'write:workflows', + }), + ], + request: { + params: z.object({ id: z.string().min(1) }), + body: { + required: true, + content: { + 'application/json': { + schema: updateWorkflowSchema, + }, + }, + }, + }, + responses: { + 200: { + description: 'Successfully updated a workflow.', + content: { + 'application/json': { + schema: createResponseSchema(workflowSchema), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 404: jsonErrorResponse('Workflow not found'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to update workflow'), + }, + }), + async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) + const { id } = c.req.valid('param') + const data = c.req.valid('json') + const [record] = await db + .update(workflows) + .set(data) + .where(and(eq(workflows.id, id), eq(workflows.userId, userId))) + .returning() + + if (!record) { + throw workflowNotFoundError() + } + + const fullRecord = await fetchFullWorkflowOrThrow(userId, record.id) + + return generateJsonResponse(c, fullRecord, 200, 'Workflow updated') + }, + ) + + .openapi( + createRoute({ + description: 'Delete a workflow.', + method: 'delete', + path: '/:id', + middleware: [ + authMiddleware({ + permission: 'write:workflows', + }), + ], + request: { + params: z.object({ id: z.string().min(1) }), + }, + responses: { + 200: { + description: 'Successfully deleted a workflow.', + content: { + 'application/json': { + schema: createResponseSchema(workflowSchema), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 404: jsonErrorResponse('Workflow not found'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to delete workflow'), + }, + }), + async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) + const { id } = c.req.valid('param') + const record = await fetchFullWorkflowOrThrow(userId, id) + + await db + .delete(workflows) + .where(and(eq(workflows.id, id), eq(workflows.userId, userId))) + + return generateJsonResponse(c, record, 200, 'Workflow deleted') + }, + ) + +export default app diff --git a/apps/server/src/schemas/db.ts b/apps/server/src/schemas/db.ts index 50867fd8..2b5e58ad 100644 --- a/apps/server/src/schemas/db.ts +++ b/apps/server/src/schemas/db.ts @@ -16,6 +16,7 @@ import { import { multiPolygon } from './customTypes' export * from './auth' +import { user } from './auth' const baseColumns = { id: text('id').primaryKey(), @@ -556,6 +557,38 @@ export const dashboard = pgTable( ], ) +export const workflowStatus = pgEnum('workflow_status', [ + 'Started', + 'Succeeded', + 'Failed', + 'Error', +]) + +export const workflows = pgTable( + 'workflows', + { + id: text('id').primaryKey(), + name: text('name').notNull(), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + status: workflowStatus('status').notNull(), + message: text('message'), + inputParameters: jsonb('input_parameters').notNull(), + createdAt: timestamp('created_at', { withTimezone: false }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: false }) + .defaultNow() + .notNull(), + completedAt: timestamp('completed_at', { withTimezone: false }), + }, + (table) => [ + index('idx_workflows_user_id').on(table.userId), + index('idx_workflows_id_user_id').on(table.id, table.userId), + ], +) + // Relations export const datasetRelations = relations(dataset, ({ many, one }) => ({ runs: many(datasetRun), @@ -808,3 +841,14 @@ export const productOutputSummaryIndicatorRelations = relations( }), }), ) + +export const workflowRelations = relations(workflows, ({ one }) => ({ + user: one(user, { + fields: [workflows.userId], + references: [user.id], + }), +})) + +export const userRelations = relations(user, ({ many }) => ({ + workflows: many(workflows), +})) diff --git a/apps/web/app/console/layout.tsx b/apps/web/app/console/layout.tsx index 2a48f7b0..ac394872 100644 --- a/apps/web/app/console/layout.tsx +++ b/apps/web/app/console/layout.tsx @@ -5,6 +5,7 @@ import { DASHBOARDS_BASE_PATH, DATASETS_BASE_PATH, GEOMETRIES_BASE_PATH, + WORKFLOWS_BASE_PATH, PRODUCTS_BASE_PATH, REPORTS_BASE_PATH, USERS_BASE_PATH, @@ -20,6 +21,12 @@ const SIDEBAR_CONFIG = [ href: USERS_BASE_PATH, roles: ['admin'], }, + { + text: 'Workflows', + icon: , + href: WORKFLOWS_BASE_PATH, + roles: ['admin', 'user'], + }, { text: 'Datasets', icon: , diff --git a/apps/web/app/console/workflow/[workflowsId]/client.tsx b/apps/web/app/console/workflow/[workflowsId]/client.tsx new file mode 100644 index 00000000..e0807233 --- /dev/null +++ b/apps/web/app/console/workflow/[workflowsId]/client.tsx @@ -0,0 +1,154 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { updateWorkflowSchema } from '@repo/schemas/crud' +import { useEffect } from 'react' +import { useForm } from 'react-hook-form' +import { usePathname } from 'next/navigation' +import { CrudForm } from '../../../../components/form/crud-form' +import { WORKFLOWS_BASE_PATH } from '../../../../lib/paths' +import { SourcesCard } from '../../_components/sources-card' +import { useDeleteWorkflow, useWorkflow, useUpdateWorkflow } from '../_hooks' +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@repo/ui/components/ui/form' +import { Input } from '@repo/ui/components/ui/input' +import { TrafficLightStatus } from '../client' + +function camelCaseToTitleCase(str: string) { + return str + .replace(/([A-Z])/g, ' $1') // Add space before capital letters + .replace(/^./, (s) => s.toUpperCase()) // Capitalize the first letter +} + +const WorkflowDetails = () => { + const pathname = usePathname() + let workflowId = pathname.split('/').pop() + if (workflowId === 'workflow') workflowId = undefined + + const { data: workflowFromUrl } = useWorkflow(workflowId) + + const workflow = workflowFromUrl + + const updateWorkflow = useUpdateWorkflow(workflowId) + const deleteWorkflow = useDeleteWorkflow(workflowId, WORKFLOWS_BASE_PATH) + + const form = useForm({ + resolver: zodResolver(updateWorkflowSchema), + }) + + useEffect(() => { + if (workflow) { + form.reset(workflow) + } + }, [workflow, form]) + + return ( +
+
+
+
+ {workflow && } +
+
+
+ + ( + + Input Parameters + + { + let val = e.target.value + try { + val = JSON.parse(val) + } catch {} + field.onChange(val) + }} + /> + + + + )} + /> + {[ + 'status', + 'message', + 'duration', + 'createdAt', + 'updatedAt', + 'completedAt', + ].map((fieldName) => ( + ( + + {camelCaseToTitleCase(fieldName)} + {fieldName === 'status' && ( + <> + + + + )} + + + + + {fieldName === 'status' && + (field.value === 'Failed' || field.value === 'Error') && ( +

+ This workflow failed. Check the message field for details.{' '} + + Click here to contact support + +

+ )} +
+ )} + /> + ))} +
+
+ ) +} + +export default WorkflowDetails diff --git a/apps/web/app/console/workflow/[workflowsId]/page.tsx b/apps/web/app/console/workflow/[workflowsId]/page.tsx new file mode 100644 index 00000000..6267eb9a --- /dev/null +++ b/apps/web/app/console/workflow/[workflowsId]/page.tsx @@ -0,0 +1,10 @@ +import PageAuthGuard from '~/components/page-auth-guard' +import ClientPage from './client' + +export default () => { + return ( + + + + ) +} diff --git a/apps/web/app/console/workflow/_components/breadcrumbs.tsx b/apps/web/app/console/workflow/_components/breadcrumbs.tsx new file mode 100644 index 00000000..d30f83fe --- /dev/null +++ b/apps/web/app/console/workflow/_components/breadcrumbs.tsx @@ -0,0 +1,51 @@ +'use client' +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from '@repo/ui/components/ui/breadcrumb' +import { usePathname } from 'next/navigation' +import Link from '../../../../components/link' +import { WORKFLOWS_BASE_PATH } from '../../../../lib/paths' +import { useWorkflow } from '../_hooks' +import { WorkflowButton } from './workflow-button' + +export const WorkflowBreadcrumbs = () => { + const pathname = usePathname() + let maybeWorkflowId = pathname.split('/').pop() + if (maybeWorkflowId === 'workflow') maybeWorkflowId = undefined + + const { data: workflowFromUrl } = useWorkflow(maybeWorkflowId) + + const workflow = workflowFromUrl + + return ( + + + + + Home + + + + + + Workflows + + + {workflow && ( + <> + + + + + + + + )} + + + ) +} diff --git a/apps/web/app/console/workflow/_components/workflow-button.tsx b/apps/web/app/console/workflow/_components/workflow-button.tsx new file mode 100644 index 00000000..ce3ebda3 --- /dev/null +++ b/apps/web/app/console/workflow/_components/workflow-button.tsx @@ -0,0 +1,30 @@ +import { BadgeLink } from '../../../../components/badge-link' +import { WorkflowLinkParams, useWorkflowLink } from '../_hooks' + +export const WorkflowButtons = ({ + workflowSets, +}: { + workflowsSets: WorkflowLinkParams[] | undefined +}) => { + return ( +
+ {workflowSets?.map((workflow) => ( + + ))} +
+ ) +} + +export const WorkflowButton = ({ + workflow, +}: { + workflow: WorkflowLinkParams +}) => { + const workflowLink = useWorkflowLink() + + return ( + + {workflow.name} + + ) +} diff --git a/apps/web/app/console/workflow/_hooks.ts b/apps/web/app/console/workflow/_hooks.ts new file mode 100644 index 00000000..7755e3d7 --- /dev/null +++ b/apps/web/app/console/workflow/_hooks.ts @@ -0,0 +1,212 @@ +'use client' + +import { workflowQuerySchema } from '@repo/schemas/crud' +import { + useInfiniteQuery, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query' +import { InferRequestType, InferResponseType } from 'hono/client' +import { useParams, useRouter } from 'next/navigation' +import { useCallback, useMemo } from 'react' +import { z } from 'zod' +import { Client, unwrapResponse } from '~/utils/apiClient' +import { getSearchParams } from '~/utils/browser' +import { useApiClient } from '../../../hooks/useApiClient' +import { mergePaginatedInfiniteData } from '../../../hooks/mergePaginatedInfiniteData' +import { useQueryWithSearchParams } from '../../../hooks/useSearchParams' +import { WORKFLOWS_BASE_PATH } from '../../../lib/paths' + +export type WorkflowListResponse = NonNullable< + InferResponseType['data'] +> +export type WorkflowListItem = WorkflowListResponse['data'][0] +export type WorkflowDetail = NonNullable< + InferResponseType['data'] +> + +export type UpdateWorkflowPayload = NonNullable< + InferRequestType['json'] +> + +export type CreateWorkflowPayload = NonNullable< + InferRequestType['json'] +> + +const workflowParamsSchema = z.object({ + workflowId: z.string().optional(), +}) + +export const workflowQueryKeys = { + all: ['workflow'] as const, + list: (query: z.infer | undefined) => + [...workflowQueryKeys.all, 'list', { query }] as const, + detail: (workflowId: string | undefined) => + [...workflowQueryKeys.all, 'detail', workflowId] as const, +} + +export const useWorkflowParams = (_workflowId?: string) => { + const params = useParams() + const { workflowId } = workflowParamsSchema.parse(params) + + return { + workflowId: _workflowId ?? workflowId, + } +} + +export const useAllWorkflows = ( + _query?: z.infer, + useSearchParams?: boolean, +) => { + const client = useApiClient() + const { query, setSearchParams } = useQueryWithSearchParams( + workflowQuerySchema, + _query, + useSearchParams, + ) + + const queryResult = useInfiniteQuery({ + queryKey: workflowQueryKeys.list(query), + queryFn: async ({ pageParam = 1 }) => { + const res = client.api.v0.workflow.$get({ + query: { + ...query, + page: pageParam, + }, + }) + + const json = await unwrapResponse(res) + + return json.data + }, + initialPageParam: 1, + getNextPageParam: (lastPage, allPages) => { + if (!lastPage) return undefined + const nextPage = allPages.length + 1 + return nextPage <= lastPage.pageCount ? nextPage : undefined + }, + }) + + const aggregatedData = useMemo( + () => mergePaginatedInfiniteData(queryResult.data), + [queryResult.data], + ) + + return { + ...queryResult, + data: aggregatedData, + query, + setSearchParams, + } +} + +export const useWorkflow = (_workflowId?: string, enabled: boolean = true) => { + const { workflowId } = useWorkflowParams(_workflowId) + const client = useApiClient() + return useQuery({ + queryKey: workflowQueryKeys.detail(workflowId), + queryFn: async () => { + if (!workflowId) return null + const res = client.api.v0.workflow[':id'].$get({ + param: { + id: workflowId, + }, + }) + + const json = await unwrapResponse(res) + + return json.data + }, + enabled: enabled ?? !!workflowId, + }) +} + +export const useCreateWorkflow = () => { + const queryClient = useQueryClient() + const client = useApiClient() + return useMutation({ + mutationFn: async (data: CreateWorkflowPayload) => { + const res = client.api.v0.workflow.$post({ + json: data, + }) + await unwrapResponse(res, 201) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: workflowQueryKeys.all, + }) + }, + }) +} + +export const useUpdateWorkflow = (_workflowId?: string) => { + const { workflowId } = useWorkflowParams(_workflowId) + const queryClient = useQueryClient() + const client = useApiClient() + return useMutation({ + mutationFn: async (payload: UpdateWorkflowPayload) => { + if (!workflowId) return + const res = client.api.v0.workflow[':id'].$patch({ + param: { id: workflowId }, + json: payload, + }) + return await unwrapResponse(res) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: workflowQueryKeys.all, + }) + }, + }) +} + +export const useDeleteWorkflow = ( + _workflowId?: string, + redirect: string | null = null, +) => { + const { workflowId } = useWorkflowParams(_workflowId) + const queryClient = useQueryClient() + const router = useRouter() + const client = useApiClient() + return useMutation({ + mutationFn: async () => { + if (!workflowId) return + const res = client.api.v0.workflow[':id'].$delete({ + param: { + id: workflowId, + }, + }) + + return await unwrapResponse(res) + }, + onSuccess: (response) => { + queryClient.removeQueries({ + queryKey: workflowQueryKeys.detail(response?.data?.id), + }) + + queryClient.invalidateQueries({ + queryKey: workflowQueryKeys.all, + }) + + if (redirect) { + router.push(redirect) + } + }, + }) +} + +export type WorkflowLinkParams = Pick + +export const useAllWorkflowLink = () => + useCallback( + (query?: z.infer) => + `${WORKFLOWS_BASE_PATH}?${getSearchParams(query ?? {})}`, + [], + ) + +export const useWorkflowLink = () => + useCallback( + (workflow: WorkflowLinkParams) => `${WORKFLOWS_BASE_PATH}/${workflow.id}`, + [], + ) diff --git a/apps/web/app/console/workflow/client.tsx b/apps/web/app/console/workflow/client.tsx new file mode 100644 index 00000000..241ad4ab --- /dev/null +++ b/apps/web/app/console/workflow/client.tsx @@ -0,0 +1,179 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@repo/ui/components/ui/form' +import { Input } from '@repo/ui/components/ui/input' +import { useMemo } from 'react' +import { useForm } from 'react-hook-form' +import Pagination from '~/components/table/pagination' +import CrudFormDialog from '../../../components/form/crud-form-dialog' +import BaseCrudTable from '../../../components/table/crud-table' +import { WorkflowButton } from './_components/workflow-button' +import { + useAllWorkflows, + useCreateWorkflow, + useWorkflowLink, + WorkflowListItem, +} from './_hooks' +import { createWorkflowSchema } from '@repo/schemas/crud' +import { SearchInput } from '../../../components/table/search-input' +import { ColumnDef, createColumnHelper } from '@tanstack/react-table' + +export const TrafficLightStatus = ({ status }: { status?: string }) => { + let color = 'bg-gray-400' + if (status === 'Succeeded') color = 'bg-green-500' + else if (status === 'Failed' || status === 'Error') color = 'bg-red-500' + else if (status === 'Started') color = 'bg-yellow-500' + + return +} + +const columnHelper = createColumnHelper() +const columns = [ + columnHelper.accessor((row) => row.duration, { + id: 'duration', + header: () => Duration, + cell: (info) => { + return info.getValue() + }, + size: 120, + }), + columnHelper.accessor((row) => row.completedAt, { + id: 'completedAt', + header: () => Completed At, + cell: (info) => { + const value = info.getValue() + if (!value) return null + return new Date(value).toLocaleDateString() + }, + size: 120, + }), + columnHelper.accessor((row) => row.status, { + id: 'status', + header: () => Status, + cell: (info) => { + const value = info.getValue() + if (!value) return null + return ( + + + {value} + + ) + }, + size: 120, + }), + columnHelper.accessor((row) => row.message, { + id: 'message', + header: () => Message, + cell: (info) => { + return info.getValue() + }, + size: 120, + }), + columnHelper.accessor((row) => row.inputParameters, { + id: 'inputParameters', + header: () => Input Parameters, + cell: (info) => { + const value = info.getValue() + if (!value) return null + return
{JSON.stringify(value, null, 2)}
+ }, + size: 120, + }), +] as ColumnDef[] + +const WorkflowFeature = () => { + const { + data, + query, + setSearchParams, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useAllWorkflows(undefined, true) + const createWorkflow = useCreateWorkflow() + const workflowLink = useWorkflowLink() + + const baseColumns = useMemo(() => { + return ['id', 'createdAt', 'updatedAt'] as const + }, []) + + const form = useForm({ + resolver: zodResolver(createWorkflowSchema), + }) + + return ( +
+
+

Workflows

+ + ( + + Source URL + + + + + + )} + /> + ( + + Source Metadata URL + + + + + + )} + /> + +
+
+ setSearchParams({ search: e.target.value })} + /> + } + query={query} + onSortChange={setSearchParams} + /> + fetchNextPage()} + /> +
+
+ ) +} + +export default WorkflowFeature diff --git a/apps/web/app/console/workflow/layout.tsx b/apps/web/app/console/workflow/layout.tsx new file mode 100644 index 00000000..cb723b84 --- /dev/null +++ b/apps/web/app/console/workflow/layout.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import DetailLayout from '../../../components/detail-layout' +import { WorkflowBreadcrumbs } from './_components/breadcrumbs' + +const WorkflowLayout: React.FC<{ + children?: React.ReactNode +}> = async ({ children }) => { + return ( + }> + {children} + + ) +} + +export default WorkflowLayout diff --git a/apps/web/app/console/workflow/page.tsx b/apps/web/app/console/workflow/page.tsx new file mode 100644 index 00000000..6267eb9a --- /dev/null +++ b/apps/web/app/console/workflow/page.tsx @@ -0,0 +1,10 @@ +import PageAuthGuard from '~/components/page-auth-guard' +import ClientPage from './client' + +export default () => { + return ( + + + + ) +} diff --git a/apps/web/lib/paths.ts b/apps/web/lib/paths.ts index 8ec04fcf..968ae783 100644 --- a/apps/web/lib/paths.ts +++ b/apps/web/lib/paths.ts @@ -1,6 +1,7 @@ export const DASHBOARDS_BASE_PATH = '/console/dashboard' export const DATASETS_BASE_PATH = '/console/dataset' export const DATASETS_RUNS_BASE_PATH = '/console/dataset/run' +export const WORKFLOWS_BASE_PATH = '/console/workflow' export const GEOMETRIES_BASE_PATH = '/console/geometries' export const GEOMETRIES_RUNS_BASE_PATH = '/console/geometries/run' export const GEOMETRIES_RUNS_OUTPUTS_BASE_PATH = '/console/geometries/output' diff --git a/packages/schemas/src/crud.ts b/packages/schemas/src/crud.ts index b45ec774..8252815c 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -159,6 +159,44 @@ export const updateIndicatorCategorySchema = baseUpdateResourceSchema.extend({ displayOrder: z.number().optional(), }) +/* WORKFLOW RESOURCE SCHEMAS */ +export const baseWorkflowSchema = z + .object({ + id: z.string().min(1), + name: z.string(), + userId: z.string(), + status: z.enum(['Started', 'Succeeded', 'Failed', 'Error']), + message: z.string().optional(), + inputParameters: z.any(), // jsonb + }) + .openapi('baseWorkflowSchema') + +export const workflowSchema = baseWorkflowSchema + .extend({ + duration: z.string().nullable(), + createdAt: z.iso.datetime().nullable(), + updatedAt: z.iso.datetime().nullable(), + completedAt: z.iso.datetime().nullable(), + }) + .openapi('WorkflowSchema') + +export const createWorkflowSchema = baseWorkflowSchema + .partial() + .extend({ + sourceUrl: z.string().min(1), + sourceMetadataUrl: z.string().min(1), + }) + .openapi('CreateWorkflowSchema') + +export const updateWorkflowSchema = baseWorkflowSchema + .partial() + .extend({ + completedAt: z.iso.datetime().nullable().optional(), + }) + .openapi('UpdateWorkflowSchema') + +export const workflowQuerySchema = baseQuerySchema + /* DATASET RESOURCE SCHEMAS */ export const baseDatasetRunSchema = baseRunResourceSchema .extend({ diff --git a/packages/ui/src/components/ui/badge.tsx b/packages/ui/src/components/ui/badge.tsx index e0cf828e..5ed7e2f4 100644 --- a/packages/ui/src/components/ui/badge.tsx +++ b/packages/ui/src/components/ui/badge.tsx @@ -27,6 +27,8 @@ const badgeVariants = cva( 'border-transparent bg-geometriesRun text-geometriesRun-foreground [a&]:hover:bg-geometriesRun/90', indicator: 'border-transparent bg-indicator text-indicator-foreground [a&]:hover:bg-indicator/90', + workflow: + 'border-transparent bg-workflow text-workflow-foreground [a&]:hover:bg-workflow/90', destructive: 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', outline: diff --git a/packages/ui/src/globals.css b/packages/ui/src/globals.css index ed1c4c98..9fb77b2e 100644 --- a/packages/ui/src/globals.css +++ b/packages/ui/src/globals.css @@ -49,6 +49,8 @@ --product-run-foreground: 240 5.9% 10%; --indicator: 60 100% 80%; --indicator-foreground: 240 5.9% 10%; + --workflow: 142 71% 45%; + --workflow-foreground: 144 100% 99%; } .dark { @@ -84,6 +86,7 @@ --product: #6a3d9a; --product-run: #cab2d6; --indicator: #33a02c; + --workflow: #385c19; } } diff --git a/packages/ui/tailwind.config.ts b/packages/ui/tailwind.config.ts index c2b184ca..4ba139e8 100644 --- a/packages/ui/tailwind.config.ts +++ b/packages/ui/tailwind.config.ts @@ -86,6 +86,10 @@ const config = { DEFAULT: 'hsl(var(--indicator))', foreground: 'hsl(var(--indicator-foreground))', }, + workflow: { + DEFAULT: 'hsl(var(--workflow))', + foreground: 'hsl(var(--workflow-foreground))', + }, }, borderRadius: { lg: 'var(--radius)',