From 956733ab605769a14f5931e7c4f7cfad1f4e3645 Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 10:34:02 +1100 Subject: [PATCH 01/15] Add workflows table --- apps/server/src/schemas/db.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apps/server/src/schemas/db.ts b/apps/server/src/schemas/db.ts index 50867fd8..17870716 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,34 @@ 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(), + inputParameters: jsonb('input_parameters').notNull(), + createdAt: timestamp('created_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), From 0b3c8f100e46976a8f6dfaadfaee74ef7853d573 Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 10:56:24 +1100 Subject: [PATCH 02/15] Add a drizzle migration --- apps/server/drizzle/0025_tranquil_cargill.sql | 14 + apps/server/drizzle/meta/0025_snapshot.json | 389 +++++------------- apps/server/drizzle/meta/_journal.json | 4 +- 3 files changed, 122 insertions(+), 285 deletions(-) create mode 100644 apps/server/drizzle/0025_tranquil_cargill.sql diff --git a/apps/server/drizzle/0025_tranquil_cargill.sql b/apps/server/drizzle/0025_tranquil_cargill.sql new file mode 100644 index 00000000..72d73676 --- /dev/null +++ b/apps/server/drizzle/0025_tranquil_cargill.sql @@ -0,0 +1,14 @@ +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, + "input_parameters" jsonb NOT NULL, + "created_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/0025_snapshot.json b/apps/server/drizzle/meta/0025_snapshot.json index 48cb56d3..a97dba38 100644 --- a/apps/server/drizzle/meta/0025_snapshot.json +++ b/apps/server/drizzle/meta/0025_snapshot.json @@ -1,5 +1,5 @@ { - "id": "1840126a-72fc-4a1f-939c-fc6bfc5425f3", + "id": "fe4fae5c-78c2-49a5-9cd5-5e08404d6984", "prevId": "387ea224-9088-4411-987c-981ed5b0e430", "version": "7", "dialect": "postgresql", @@ -53,31 +53,7 @@ "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": {} - } - }, + "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {}, @@ -147,29 +123,6 @@ } }, "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": [ @@ -336,29 +289,6 @@ } }, "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": [ @@ -480,36 +410,6 @@ } }, "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": [ @@ -705,29 +605,6 @@ } }, "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": [ @@ -900,29 +777,6 @@ } }, "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": [ @@ -1146,36 +1000,6 @@ } }, "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": [ @@ -1448,36 +1272,6 @@ } }, "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": [ @@ -1681,27 +1475,6 @@ } }, "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": [ @@ -2328,50 +2101,6 @@ } }, "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": [ @@ -2737,32 +2466,116 @@ "notNull": false } }, + "indexes": {}, + "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 + }, + "input_parameters": { + "name": "input_parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, "indexes": { - "report_search_trgm_idx": { - "name": "report_search_trgm_idx", + "idx_workflows_user_id": { + "name": "idx_workflows_user_id", "columns": [ { - "expression": "name", + "expression": "user_id", "isExpression": false, "asc": true, - "nulls": "last", - "opclass": "gin_trgm_ops" + "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": "description", + "expression": "user_id", "isExpression": false, "asc": true, - "nulls": "last", - "opclass": "gin_trgm_ops" + "nulls": "last" } ], "isUnique": false, "concurrently": false, - "method": "gin", + "method": "btree", "with": {} } }, - "foreignKeys": {}, + "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": {}, @@ -3761,6 +3574,16 @@ "month", "year" ] + }, + "public.workflow_status": { + "name": "workflow_status", + "schema": "public", + "values": [ + "Started", + "Succeeded", + "Failed", + "Error" + ] } }, "schemas": {}, diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index a77e9bb6..98c8a894 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -180,8 +180,8 @@ { "idx": 25, "version": "7", - "when": 1772420069848, - "tag": "0025_hesitant_christian_walker", + "when": 1770852904769, + "tag": "0025_tranquil_cargill", "breakpoints": true } ] From 64bbd762e1303c02f410d9bfb7fa0fec0bfab4cc Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 11:40:33 +1100 Subject: [PATCH 03/15] WIP: Add draft of workflows endpoints. --- apps/server/src/routes/workflow.ts | 352 +++++++++++++++++++++++++++++ packages/schemas/src/crud.ts | 27 +++ 2 files changed, 379 insertions(+) create mode 100644 apps/server/src/routes/workflow.ts diff --git a/apps/server/src/routes/workflow.ts b/apps/server/src/routes/workflow.ts new file mode 100644 index 00000000..3d7e956f --- /dev/null +++ b/apps/server/src/routes/workflow.ts @@ -0,0 +1,352 @@ +import { createRoute, z } from '@hono/zod-openapi' +import { count, 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, +} from '@repo/schemas/crud' + +const workflowsQuery = (userId: string, id?: string) => + ({ + columns: { + id: true, + name: true, + userId: true, + status: true, + inputParameters: true, + createdAt: true, + completedAt: true, + }, + where: (workflows, { eq, and }) => + id + ? and(eq(workflows.userId, userId), eq(workflows.id, id)) + : eq(workflows.userId, userId), + }) satisfies QueryForTable<'workflows'> + +const workflowsNotFoundError = () => + new ServerError({ + statusCode: 404, + message: 'Failed to get workflow', + description: "Workflow you're looking for is not found", + }) + +const fetchFullworkflows = async (userId: string, id?: string) => { + const record = await db.query.workflows.findFirst({ + ...workflowsQuery(userId, id), + }) + return record ?? null +} + +const fetchFullworkflowsOrThrow = async (userId: string, id?: string) => { + const record = await fetchFullworkflows(userId, id) + + if (!record) { + throw workflowsNotFoundError() + } + + return record +} + +const app = createOpenAPIApp() + .openapi( + createRoute({ + description: 'List all workflows.', + method: 'get', + path: '/', + middleware: [ + authMiddleware({ + permission: 'read:workflows', + }), + ], + request: { + query: z.object({ userId: z.string().min(1) }), + }, + responses: { + 200: { + description: 'Successfully listed all workflows.', + content: { + 'application/json': { + schema: createResponseSchema( + z.object({ + data: z.array(workflowSchema), + totalCount: z.number().int(), + }), + ), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 500: jsonErrorResponse('Failed to list workflows'), + }, + }), + async (c) => { + const { userId } = c.req.valid('query') + const totalCount = await db + .select({ + count: count(), + }) + .from(workflows) + + const data = await db.query.workflows.findMany({ + ...workflowsQuery(userId), + orderBy: desc(workflows.createdAt), + }) + + return generateJsonResponse( + c, + { + data, + totalCount: totalCount[0]!.count, + }, + 200, + ) + }, + ) + + .openapi( + createRoute({ + description: 'Get a single workflow.', + method: 'get', + path: '/:id', + middleware: [authMiddleware({ permission: 'read:workflows' })], + request: { + params: z.object({ id: z.string().min(1) }), + query: z.object({ userId: 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 { id } = c.req.valid('param') + const { userId } = c.req.valid('query') + const record = await fetchFullworkflowsOrThrow(userId, id) + + return generateJsonResponse(c, record, 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 data = c.req.valid('json') + + // TODO: Should this be included in this endpoint or a seperate endpoint? It needs to be called here to get the id before writing 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, + } + } + + // Submit workflow to Argo + let argoWorkflow + try { + argoWorkflow = await submitToArgoWorkflows(data.inputParameters) + } catch (err) { + throw err + } + + // Insert workflow record using Argo response + const workflowRecord = { + ...data, + id: argoWorkflow.id, + name: argoWorkflow.name, + } + const [newworkflows] = await db + .insert(workflows) + .values(workflowRecord) + .returning() + + if (!newworkflows) { + throw new ServerError({ + statusCode: 500, + message: 'Failed to create workflows', + description: 'workflow insert did not return a record', + }) + } + + const record = await fetchFullworkflowsOrThrow( + newworkflows.userId, + newworkflows.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) }), + query: z.object({ userId: z.string().min(1) }), + body: { + required: true, + content: { + 'application/json': { + schema: updateWorkflowSchema, + }, + }, + }, + }, + responses: { + 200: { + description: 'Successfully updated a workflow.', + content: { + 'application/json': { + schema: createResponseSchema(z.any()), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 404: jsonErrorResponse('workflow not found'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to update workflow'), + }, + }), + async (c) => { + const { id } = c.req.valid('param') + const { userId } = c.req.valid('query') + const data = c.req.valid('json') + const [record] = await db + .update(workflows) + .set(data) + .where(eq(workflows.id, id)) + .returning() + + if (!record) { + throw workflowsNotFoundError() + } + + const fullRecord = await fetchFullworkflowsOrThrow(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) }), + query: z.object({ userId: z.string().min(1) }), + }, + responses: { + 200: { + description: 'Successfully deleted a workflow.', + content: { + 'application/json': { + schema: createResponseSchema(z.any()), + }, + }, + }, + 401: jsonErrorResponse('Unauthorized'), + 404: jsonErrorResponse('workflow not found'), + 422: validationErrorResponse, + 500: jsonErrorResponse('Failed to delete workflow'), + }, + }), + async (c) => { + const { id } = c.req.valid('param') + const { userId } = c.req.valid('query') + const record = await fetchFullworkflowsOrThrow(userId, id) + + await db.delete(workflows).where(eq(workflows.id, id)) + + return generateJsonResponse(c, record, 200, 'workflow deleted') + }, + ) + +export default app diff --git a/packages/schemas/src/crud.ts b/packages/schemas/src/crud.ts index b45ec774..cedf73e5 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -159,6 +159,33 @@ export const updateIndicatorCategorySchema = baseUpdateResourceSchema.extend({ displayOrder: z.number().optional(), }) +/* WORKFLOW RESOURCE SCHEMAS */ +export const workflowBase = z + .object({ + id: z.string().min(1), + name: z.string(), + userId: z.string(), + status: z.enum(['Started', 'Succeeded', 'Failed', 'Error']), + inputParameters: z.any(), + }) + .openapi('WorkflowSchemaBase') + +export const workflowSchema = workflowBase + .extend({ + createdAt: z.iso.datetime().nullable(), + completedAt: z.iso.datetime().nullable(), + }) + .openapi('WorkflowSchema') + +export const createWorkflowSchema = workflowBase.openapi('CreateWorkflowSchema') + +export const updateWorkflowSchema = workflowBase + .partial() + .extend({ + completedAt: z.iso.datetime().nullable().optional(), + }) + .openapi('UpdateWorkflowSchema') + /* DATASET RESOURCE SCHEMAS */ export const baseDatasetRunSchema = baseRunResourceSchema .extend({ From 67c5404f1f8021ae3d4c4fce043828ce7836c2bb Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 14:31:05 +1100 Subject: [PATCH 04/15] Add workflows page and endpoints. Works for listing. --- apps/server/src/app.ts | 2 + .../src/routes/{workflow.ts => workflows.ts} | 63 +++-- apps/web/app/console/layout.tsx | 7 + .../workflows/[workflowsId]/client.tsx | 48 ++++ .../console/workflows/[workflowsId]/page.tsx | 10 + .../workflows/_components/breadcrumbs.tsx | 49 ++++ .../_components/workflows-button.tsx | 30 +++ .../_components/workflows-select.tsx | 49 ++++ apps/web/app/console/workflows/_hooks.ts | 237 ++++++++++++++++++ apps/web/app/console/workflows/client.tsx | 107 ++++++++ apps/web/app/console/workflows/layout.tsx | 15 ++ apps/web/app/console/workflows/page.tsx | 10 + apps/web/lib/paths.ts | 1 + packages/schemas/src/crud.ts | 18 +- 14 files changed, 613 insertions(+), 33 deletions(-) rename apps/server/src/routes/{workflow.ts => workflows.ts} (85%) create mode 100644 apps/web/app/console/workflows/[workflowsId]/client.tsx create mode 100644 apps/web/app/console/workflows/[workflowsId]/page.tsx create mode 100644 apps/web/app/console/workflows/_components/breadcrumbs.tsx create mode 100644 apps/web/app/console/workflows/_components/workflows-button.tsx create mode 100644 apps/web/app/console/workflows/_components/workflows-select.tsx create mode 100644 apps/web/app/console/workflows/_hooks.ts create mode 100644 apps/web/app/console/workflows/client.tsx create mode 100644 apps/web/app/console/workflows/layout.tsx create mode 100644 apps/web/app/console/workflows/page.tsx diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 063b6014..bb4d483d 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 workflows from './routes/workflows' const isProduction = env.NODE_ENV === 'production' @@ -82,6 +83,7 @@ const v0ApiRoutes = app .route('/indicator-category', indicatorCategory) .route('/report', report) .route('/dashboard', dashboard) + .route('/workflows', workflows) v0ApiRoutes.openAPIRegistry.registerComponent('securitySchemes', 'ApiKeyAuth', { type: 'apiKey', diff --git a/apps/server/src/routes/workflow.ts b/apps/server/src/routes/workflows.ts similarity index 85% rename from apps/server/src/routes/workflow.ts rename to apps/server/src/routes/workflows.ts index 3d7e956f..c5dacbdc 100644 --- a/apps/server/src/routes/workflow.ts +++ b/apps/server/src/routes/workflows.ts @@ -13,10 +13,13 @@ import { generateJsonResponse } from '../lib/response' import { workflows } from '../schemas/db' import { QueryForTable } from '../schemas/util' import { - createWorkflowSchema, - updateWorkflowSchema, - workflowSchema, + baseWorkflowsSchema, + createWorkflowsSchema, + updateWorkflowsSchema, + workflowsSchema, + workflowsQuerySchema, } from '@repo/schemas/crud' +import { parseQuery } from '~/utils/query' const workflowsQuery = (userId: string, id?: string) => ({ @@ -62,7 +65,7 @@ const fetchFullworkflowsOrThrow = async (userId: string, id?: string) => { const app = createOpenAPIApp() .openapi( createRoute({ - description: 'List all workflows.', + description: 'List all workflows for a user with pagination metadata.', method: 'get', path: '/', middleware: [ @@ -71,50 +74,58 @@ const app = createOpenAPIApp() }), ], request: { - query: z.object({ userId: z.string().min(1) }), + query: workflowsQuerySchema.extend({ + userId: z.string().min(1), + }), }, responses: { 200: { - description: 'Successfully listed all workflows.', + description: 'Successfully listed workflows.', content: { 'application/json': { schema: createResponseSchema( z.object({ - data: z.array(workflowSchema), + pageCount: z.number().int(), totalCount: z.number().int(), + data: z.array(baseWorkflowsSchema), }), ), }, }, }, 401: jsonErrorResponse('Unauthorized'), + 422: validationErrorResponse, 500: jsonErrorResponse('Failed to list workflows'), }, }), async (c) => { - const { userId } = c.req.valid('query') - const totalCount = await db - .select({ - count: count(), - }) - .from(workflows) + console.log(c) + const { userId, ...restQuery } = c.req.valid('query') + const { pageCount, totalCount, ...query } = await parseQuery( + workflows, + restQuery, + { + defaultOrderBy: desc(workflows.createdAt), + searchableColumns: [workflows.name], + }, + ) const data = await db.query.workflows.findMany({ ...workflowsQuery(userId), - orderBy: desc(workflows.createdAt), + ...query, }) return generateJsonResponse( c, { + pageCount, data, - totalCount: totalCount[0]!.count, + totalCount, }, 200, ) }, ) - .openapi( createRoute({ description: 'Get a single workflow.', @@ -130,7 +141,7 @@ const app = createOpenAPIApp() description: 'Successfully retrieved a workflow.', content: { 'application/json': { - schema: createResponseSchema(workflowSchema), + schema: createResponseSchema(workflowsSchema), }, }, }, @@ -164,7 +175,7 @@ const app = createOpenAPIApp() required: true, content: { 'application/json': { - schema: createWorkflowSchema, + schema: createWorkflowsSchema, }, }, }, @@ -174,7 +185,7 @@ const app = createOpenAPIApp() description: 'Successfully created a workflow.', content: { 'application/json': { - schema: createResponseSchema(workflowSchema), + schema: createResponseSchema(workflowsSchema), }, }, }, @@ -239,7 +250,7 @@ const app = createOpenAPIApp() throw new ServerError({ statusCode: 500, message: 'Failed to create workflows', - description: 'workflow insert did not return a record', + description: 'Workflow insert did not return a record', }) } @@ -248,7 +259,7 @@ const app = createOpenAPIApp() newworkflows.id, ) - return generateJsonResponse(c, record, 201, 'workflow created') + return generateJsonResponse(c, record, 201, 'Workflow created') }, ) @@ -269,7 +280,7 @@ const app = createOpenAPIApp() required: true, content: { 'application/json': { - schema: updateWorkflowSchema, + schema: updateWorkflowsSchema, }, }, }, @@ -284,7 +295,7 @@ const app = createOpenAPIApp() }, }, 401: jsonErrorResponse('Unauthorized'), - 404: jsonErrorResponse('workflow not found'), + 404: jsonErrorResponse('Workflow not found'), 422: validationErrorResponse, 500: jsonErrorResponse('Failed to update workflow'), }, @@ -305,7 +316,7 @@ const app = createOpenAPIApp() const fullRecord = await fetchFullworkflowsOrThrow(userId, record.id) - return generateJsonResponse(c, fullRecord, 200, 'workflow updated') + return generateJsonResponse(c, fullRecord, 200, 'Workflow updated') }, ) @@ -333,7 +344,7 @@ const app = createOpenAPIApp() }, }, 401: jsonErrorResponse('Unauthorized'), - 404: jsonErrorResponse('workflow not found'), + 404: jsonErrorResponse('Workflow not found'), 422: validationErrorResponse, 500: jsonErrorResponse('Failed to delete workflow'), }, @@ -345,7 +356,7 @@ const app = createOpenAPIApp() await db.delete(workflows).where(eq(workflows.id, id)) - return generateJsonResponse(c, record, 200, 'workflow deleted') + return generateJsonResponse(c, record, 200, 'Workflow deleted') }, ) diff --git a/apps/web/app/console/layout.tsx b/apps/web/app/console/layout.tsx index 2a48f7b0..ae91f0a5 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/workflows/[workflowsId]/client.tsx b/apps/web/app/console/workflows/[workflowsId]/client.tsx new file mode 100644 index 00000000..c33257eb --- /dev/null +++ b/apps/web/app/console/workflows/[workflowsId]/client.tsx @@ -0,0 +1,48 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { updateWorkflowsSchema } from '@repo/schemas/crud' +import { useEffect } from 'react' +import { useForm } from 'react-hook-form' +import { CrudForm } from '../../../../components/form/crud-form' +import { WORKFLOWS_BASE_PATH } from '../../../../lib/paths' +import { SourcesCard } from '../../_components/sources-card' +import { useDeleteWorkflows, useWorkflows, useUpdateWorkflows } from '../_hooks' + +const WorkflowsDetails = () => { + const { data: workflows } = useWorkflows() + const updateWorkflows = useUpdateWorkflows() + const deleteWorkflows = useDeleteWorkflows(undefined, WORKFLOWS_BASE_PATH) + + const form = useForm({ + resolver: zodResolver(updateWorkflowsSchema), + }) + + useEffect(() => { + if (workflows) { + form.reset(workflows) + } + }, [workflows, form]) + + return ( +
+
+
+
+ {workflows && } +
+
+
+ +
+ ) +} + +export default WorkflowsDetails diff --git a/apps/web/app/console/workflows/[workflowsId]/page.tsx b/apps/web/app/console/workflows/[workflowsId]/page.tsx new file mode 100644 index 00000000..6267eb9a --- /dev/null +++ b/apps/web/app/console/workflows/[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/workflows/_components/breadcrumbs.tsx b/apps/web/app/console/workflows/_components/breadcrumbs.tsx new file mode 100644 index 00000000..fbb6f6ce --- /dev/null +++ b/apps/web/app/console/workflows/_components/breadcrumbs.tsx @@ -0,0 +1,49 @@ +'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 { useWorkflows } from '../_hooks' +import { WorkflowsButton } from './workflows-button' + +export const WorkflowsBreadcrumbs = () => { + const pathname = usePathname() + + const { data: workflowsFromUrl } = useWorkflows() + + const workflows = workflowsFromUrl + + return ( + + + + + Home + + + + + + Workflows + + + {workflows && ( + <> + + + + + + + + )} + + + ) +} diff --git a/apps/web/app/console/workflows/_components/workflows-button.tsx b/apps/web/app/console/workflows/_components/workflows-button.tsx new file mode 100644 index 00000000..b924bde1 --- /dev/null +++ b/apps/web/app/console/workflows/_components/workflows-button.tsx @@ -0,0 +1,30 @@ +import { BadgeLink } from '../../../../components/badge-link' +import { WorkflowsLinkParams, useWorkflowsLink } from '../_hooks' + +export const WorkflowsButtons = ({ + workflowsSets, +}: { + workflowsSets: WorkflowsLinkParams[] | undefined +}) => { + return ( +
+ {workflowsSets?.map((workflows) => ( + + ))} +
+ ) +} + +export const WorkflowsButton = ({ + workflows, +}: { + workflows: WorkflowsLinkParams +}) => { + const workflowsLink = useWorkflowsLink() + + return ( + + {workflows.name} + + ) +} diff --git a/apps/web/app/console/workflows/_components/workflows-select.tsx b/apps/web/app/console/workflows/_components/workflows-select.tsx new file mode 100644 index 00000000..6befae2d --- /dev/null +++ b/apps/web/app/console/workflows/_components/workflows-select.tsx @@ -0,0 +1,49 @@ +import { FieldGroup } from '../../../../components/form/action' +import { SelectWithSearch } from '@repo/ui/components/ui/select-with-search' +import { WorkflowsListItem, useAllWorkflows, useWorkflows } from '../_hooks' + +export const WorkflowsSelect = ({ + value, + onChange, + disabled, + isClearable = true, +}: { + value: string | null | undefined + onChange: (workflows: WorkflowsListItem | null) => void + disabled?: boolean + isClearable?: boolean +}) => { + const { + data: allWorkflows, + setSearchParams, + fetchNextPage, + hasNextPage, + isLoading: isLoadingWorkflows, + isFetchingNextPage, + } = useAllWorkflows() + + const { data: selectedWorkflows } = useWorkflows(value ?? undefined) + + return ( + + { + setSearchParams({ search }) + }} + onChange={(nextValue) => { + onChange(nextValue) + }} + isDisabled={disabled} + isLoading={isLoadingWorkflows || isFetchingNextPage} + onMenuScrollToBottom={() => { + if (hasNextPage) { + fetchNextPage() + } + }} + isClearable={isClearable} + /> + + ) +} diff --git a/apps/web/app/console/workflows/_hooks.ts b/apps/web/app/console/workflows/_hooks.ts new file mode 100644 index 00000000..7893a39c --- /dev/null +++ b/apps/web/app/console/workflows/_hooks.ts @@ -0,0 +1,237 @@ +'use client' + +import { workflowsQuerySchema } 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' +import { useAuthClient } from '~/hooks/useAuthClient' + +export type WorkflowsListResponse = NonNullable< + InferResponseType['data'] +> +export type WorkflowsListItem = WorkflowsListResponse['data'][0] +export type WorkflowsDetail = NonNullable< + InferResponseType< + Client['api']['v0']['workflows'][':id']['$get'], + 200 + >['data'] +> + +export type UpdateWorkflowsPayload = NonNullable< + InferRequestType['json'] +> + +export type CreateWorkflowsPayload = NonNullable< + InferRequestType['json'] +> + +const workflowsParamsSchema = z.object({ + workflowsId: z.string().optional(), +}) + +export const workflowsQueryKeys = { + all: ['workflows'] as const, + list: (query: z.infer | undefined) => + [...workflowsQueryKeys.all, 'list', { query }] as const, + detail: (workflowsId: string | undefined) => + [...workflowsQueryKeys.all, 'detail', workflowsId] as const, +} + +export const useWorkflowsParams = (_workflowsId?: string) => { + const params = useParams() + const { workflowsId } = workflowsParamsSchema.parse(params) + + return { + workflowsId: _workflowsId ?? workflowsId, + } +} + +export const useAllWorkflows = ( + _query?: z.infer, + useSearchParams?: boolean, +) => { + const authClient = useAuthClient() + const { data } = authClient.useSession() + const user = data?.user + const userId = user?.id + const client = useApiClient() + const { query, setSearchParams } = useQueryWithSearchParams( + workflowsQuerySchema, + _query, + useSearchParams, + ) + + const queryResult = useInfiniteQuery({ + queryKey: workflowsQueryKeys.list(query), + queryFn: async ({ pageParam = 1 }) => { + if (!userId) { + return { + data: [], + pageCount: 0, + totalCount: 0, + error: { + status: 500, + message: 'Missing userId', + }, + } + } + const res = client.api.v0.workflows.$get({ + query: { + ...query, + userId, + 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 + }, + enabled: !!userId, + }) + + const aggregatedData = useMemo( + () => mergePaginatedInfiniteData(queryResult.data), + [queryResult.data], + ) + + return { + ...queryResult, + data: aggregatedData, + query, + setSearchParams, + } +} + +export const useWorkflows = ( + _workflowsId?: string, + enabled: boolean = true, +) => { + const { workflowsId } = useWorkflowsParams(_workflowsId) + const client = useApiClient() + return useQuery({ + queryKey: workflowsQueryKeys.detail(workflowsId), + queryFn: async () => { + if (!workflowsId) return null + const res = client.api.v0.workflows[':id'].$get({ + param: { + id: workflowsId, + }, + }) + + const json = await unwrapResponse(res) + + return json.data + }, + enabled: enabled ?? !!workflowsId, + }) +} + +export const useCreateWorkflows = () => { + const queryClient = useQueryClient() + const client = useApiClient() + return useMutation({ + mutationFn: async (data: CreateWorkflowsPayload) => { + const res = client.api.v0.workflows.$post({ + json: data, + }) + await unwrapResponse(res, 201) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: workflowsQueryKeys.all, + }) + }, + }) +} + +export const useUpdateWorkflows = (_workflowsId?: string) => { + const { workflowsId } = useWorkflowsParams(_workflowsId) + const queryClient = useQueryClient() + const client = useApiClient() + return useMutation({ + mutationFn: async (payload: UpdateWorkflowsPayload) => { + if (!workflowsId) return + const res = client.api.v0.workflows[':id'].$patch({ + param: { id: workflowsId }, + json: payload, + }) + return await unwrapResponse(res) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: workflowsQueryKeys.all, + }) + }, + }) +} + +export const useDeleteWorkflows = ( + _workflowsId?: string, + redirect: string | null = null, +) => { + const { workflowsId } = useWorkflowsParams(_workflowsId) + const queryClient = useQueryClient() + const router = useRouter() + const client = useApiClient() + return useMutation({ + mutationFn: async () => { + if (!workflowsId) return + const res = client.api.v0.workflows[':id'].$delete({ + param: { + id: workflowsId, + }, + }) + + return await unwrapResponse(res) + }, + onSuccess: (response) => { + queryClient.removeQueries({ + queryKey: workflowsQueryKeys.detail(response?.data?.id), + }) + + queryClient.invalidateQueries({ + queryKey: workflowsQueryKeys.all, + }) + + if (redirect) { + router.push(redirect) + } + }, + }) +} + +export type WorkflowsLinkParams = Pick + +export const useAllWorkflowsLink = () => + useCallback( + (query?: z.infer) => + `${WORKFLOWS_BASE_PATH}?${getSearchParams(query ?? {})}`, + [], + ) + +export const useWorkflowsLink = () => + useCallback( + (workflows: WorkflowsLinkParams) => + `${WORKFLOWS_BASE_PATH}/${workflows.id}`, + [], + ) diff --git a/apps/web/app/console/workflows/client.tsx b/apps/web/app/console/workflows/client.tsx new file mode 100644 index 00000000..029ee50a --- /dev/null +++ b/apps/web/app/console/workflows/client.tsx @@ -0,0 +1,107 @@ +'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 { WorkflowsButton } from './_components/workflows-button' +import { useAllWorkflows, useCreateWorkflows, useWorkflowsLink } from './_hooks' +import { createWorkflowsSchema } from '@repo/schemas/crud' +import { SearchInput } from '../../../components/table/search-input' + +const WorkflowsFeature = () => { + const { + data, + query, + setSearchParams, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useAllWorkflows(undefined, true) + const createWorkflows = useCreateWorkflows() + const workflowsLink = useWorkflowsLink() + + const baseColumns = useMemo(() => { + return ['description', 'createdAt', 'updatedAt'] as const + }, []) + + const form = useForm({ + resolver: zodResolver(createWorkflowsSchema), + }) + + return ( +
+
+

Workflows

+ + ( + + Source URL + + + + + + )} + /> + ( + + Source Metadata URL + + + + + + )} + /> + +
+
+ setSearchParams({ search: e.target.value })} + /> + } + query={query} + onSortChange={setSearchParams} + /> + fetchNextPage()} + /> +
+
+ ) +} + +export default WorkflowsFeature diff --git a/apps/web/app/console/workflows/layout.tsx b/apps/web/app/console/workflows/layout.tsx new file mode 100644 index 00000000..9960bee4 --- /dev/null +++ b/apps/web/app/console/workflows/layout.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import DetailLayout from '../../../components/detail-layout' +import { WorkflowsBreadcrumbs } from './_components/breadcrumbs' + +const WorkflowsLayout: React.FC<{ + children?: React.ReactNode +}> = async ({ children }) => { + return ( + }> + {children} + + ) +} + +export default WorkflowsLayout diff --git a/apps/web/app/console/workflows/page.tsx b/apps/web/app/console/workflows/page.tsx new file mode 100644 index 00000000..6267eb9a --- /dev/null +++ b/apps/web/app/console/workflows/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..f74cf341 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/workflows' 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 cedf73e5..020af7a2 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -160,7 +160,7 @@ export const updateIndicatorCategorySchema = baseUpdateResourceSchema.extend({ }) /* WORKFLOW RESOURCE SCHEMAS */ -export const workflowBase = z +export const baseWorkflowsSchema = z .object({ id: z.string().min(1), name: z.string(), @@ -168,23 +168,27 @@ export const workflowBase = z status: z.enum(['Started', 'Succeeded', 'Failed', 'Error']), inputParameters: z.any(), }) - .openapi('WorkflowSchemaBase') + .openapi('baseWorkflowsSchema') -export const workflowSchema = workflowBase +export const workflowsSchema = baseWorkflowsSchema .extend({ createdAt: z.iso.datetime().nullable(), completedAt: z.iso.datetime().nullable(), }) - .openapi('WorkflowSchema') + .openapi('WorkflowsSchema') -export const createWorkflowSchema = workflowBase.openapi('CreateWorkflowSchema') +export const createWorkflowsSchema = baseWorkflowsSchema.openapi( + 'CreateWorkflowsSchema', +) -export const updateWorkflowSchema = workflowBase +export const updateWorkflowsSchema = baseWorkflowsSchema .partial() .extend({ completedAt: z.iso.datetime().nullable().optional(), }) - .openapi('UpdateWorkflowSchema') + .openapi('UpdateWorkflowsSchema') + +export const workflowsQuerySchema = baseQuerySchema /* DATASET RESOURCE SCHEMAS */ export const baseDatasetRunSchema = baseRunResourceSchema From d7515325ede532bed06a409b7edee781b4bfd9ae Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 15:29:08 +1100 Subject: [PATCH 05/15] Move userId check to server side so user cannot see it. Only allow user to CRUD their own workflows. --- apps/server/src/routes/workflows.ts | 53 +++++++++++++++-------- apps/web/app/console/workflows/_hooks.ts | 18 -------- apps/web/app/console/workflows/client.tsx | 27 ++++++++++-- packages/schemas/src/crud.ts | 10 +++-- 4 files changed, 65 insertions(+), 43 deletions(-) diff --git a/apps/server/src/routes/workflows.ts b/apps/server/src/routes/workflows.ts index c5dacbdc..4079f852 100644 --- a/apps/server/src/routes/workflows.ts +++ b/apps/server/src/routes/workflows.ts @@ -1,5 +1,5 @@ import { createRoute, z } from '@hono/zod-openapi' -import { count, desc, eq } from 'drizzle-orm' +import { and, desc, eq } from 'drizzle-orm' import { db } from '~/lib/db' import { ServerError } from '~/lib/error' import { @@ -21,6 +21,16 @@ import { } from '@repo/schemas/crud' import { parseQuery } from '~/utils/query' +function throwIfNotAuthenticated(userId: string | undefined) { + if (!userId) { + throw new ServerError({ + statusCode: 401, + message: 'Unauthorized', + description: 'User authentication required for this action.', + }) + } +} + const workflowsQuery = (userId: string, id?: string) => ({ columns: { @@ -74,9 +84,7 @@ const app = createOpenAPIApp() }), ], request: { - query: workflowsQuerySchema.extend({ - userId: z.string().min(1), - }), + query: workflowsQuerySchema, }, responses: { 200: { @@ -99,11 +107,11 @@ const app = createOpenAPIApp() }, }), async (c) => { - console.log(c) - const { userId, ...restQuery } = c.req.valid('query') - const { pageCount, totalCount, ...query } = await parseQuery( + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) + const { pageCount, totalCount } = await parseQuery( workflows, - restQuery, + c.req.valid('query'), { defaultOrderBy: desc(workflows.createdAt), searchableColumns: [workflows.name], @@ -112,7 +120,6 @@ const app = createOpenAPIApp() const data = await db.query.workflows.findMany({ ...workflowsQuery(userId), - ...query, }) return generateJsonResponse( @@ -134,7 +141,6 @@ const app = createOpenAPIApp() middleware: [authMiddleware({ permission: 'read:workflows' })], request: { params: z.object({ id: z.string().min(1) }), - query: z.object({ userId: z.string().min(1) }), }, responses: { 200: { @@ -152,8 +158,9 @@ const app = createOpenAPIApp() }, }), async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) const { id } = c.req.valid('param') - const { userId } = c.req.valid('query') const record = await fetchFullworkflowsOrThrow(userId, id) return generateJsonResponse(c, record, 200) @@ -195,8 +202,11 @@ const app = createOpenAPIApp() }, }), 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 here to get the id before writing to the DB. // Function to POST to Argo Workflows API async function submitToArgoWorkflows(input: any) { @@ -234,12 +244,19 @@ const app = createOpenAPIApp() } 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: `argo-${Date.now()}`, + name: `workflow-${Date.now()}`, + } // Insert workflow record using Argo response const workflowRecord = { ...data, id: argoWorkflow.id, name: argoWorkflow.name, + userId, } const [newworkflows] = await db .insert(workflows) @@ -275,7 +292,6 @@ const app = createOpenAPIApp() ], request: { params: z.object({ id: z.string().min(1) }), - query: z.object({ userId: z.string().min(1) }), body: { required: true, content: { @@ -301,13 +317,14 @@ const app = createOpenAPIApp() }, }), async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) const { id } = c.req.valid('param') - const { userId } = c.req.valid('query') const data = c.req.valid('json') const [record] = await db .update(workflows) .set(data) - .where(eq(workflows.id, id)) + .where(and(eq(workflows.id, id), eq(workflows.userId, userId))) .returning() if (!record) { @@ -332,7 +349,6 @@ const app = createOpenAPIApp() ], request: { params: z.object({ id: z.string().min(1) }), - query: z.object({ userId: z.string().min(1) }), }, responses: { 200: { @@ -350,11 +366,14 @@ const app = createOpenAPIApp() }, }), async (c) => { + const userId = c.get('user')?.id + throwIfNotAuthenticated(userId) const { id } = c.req.valid('param') - const { userId } = c.req.valid('query') const record = await fetchFullworkflowsOrThrow(userId, id) - await db.delete(workflows).where(eq(workflows.id, id)) + await db + .delete(workflows) + .where(and(eq(workflows.id, id), eq(workflows.userId, userId))) return generateJsonResponse(c, record, 200, 'Workflow deleted') }, diff --git a/apps/web/app/console/workflows/_hooks.ts b/apps/web/app/console/workflows/_hooks.ts index 7893a39c..a34ff353 100644 --- a/apps/web/app/console/workflows/_hooks.ts +++ b/apps/web/app/console/workflows/_hooks.ts @@ -17,7 +17,6 @@ import { useApiClient } from '../../../hooks/useApiClient' import { mergePaginatedInfiniteData } from '../../../hooks/mergePaginatedInfiniteData' import { useQueryWithSearchParams } from '../../../hooks/useSearchParams' import { WORKFLOWS_BASE_PATH } from '../../../lib/paths' -import { useAuthClient } from '~/hooks/useAuthClient' export type WorkflowsListResponse = NonNullable< InferResponseType['data'] @@ -63,10 +62,6 @@ export const useAllWorkflows = ( _query?: z.infer, useSearchParams?: boolean, ) => { - const authClient = useAuthClient() - const { data } = authClient.useSession() - const user = data?.user - const userId = user?.id const client = useApiClient() const { query, setSearchParams } = useQueryWithSearchParams( workflowsQuerySchema, @@ -77,21 +72,9 @@ export const useAllWorkflows = ( const queryResult = useInfiniteQuery({ queryKey: workflowsQueryKeys.list(query), queryFn: async ({ pageParam = 1 }) => { - if (!userId) { - return { - data: [], - pageCount: 0, - totalCount: 0, - error: { - status: 500, - message: 'Missing userId', - }, - } - } const res = client.api.v0.workflows.$get({ query: { ...query, - userId, page: pageParam, }, }) @@ -106,7 +89,6 @@ export const useAllWorkflows = ( const nextPage = allPages.length + 1 return nextPage <= lastPage.pageCount ? nextPage : undefined }, - enabled: !!userId, }) const aggregatedData = useMemo( diff --git a/apps/web/app/console/workflows/client.tsx b/apps/web/app/console/workflows/client.tsx index 029ee50a..06867814 100644 --- a/apps/web/app/console/workflows/client.tsx +++ b/apps/web/app/console/workflows/client.tsx @@ -49,13 +49,32 @@ const WorkflowsFeature = () => { buttonText="Add Workflows" entityName="Workflows" entityNamePlural="workflows sets" + hiddenFields={[ + 'description', + 'metadata', + 'sourceUrl', + 'sourceMetadataUrl', + ]} > + {/* ( + + User ID + + + + + + )} + /> */} ( - Source URL + Status @@ -65,10 +84,10 @@ const WorkflowsFeature = () => { /> ( - Source Metadata URL + Input Parameters diff --git a/packages/schemas/src/crud.ts b/packages/schemas/src/crud.ts index 020af7a2..48284ec2 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -166,7 +166,7 @@ export const baseWorkflowsSchema = z name: z.string(), userId: z.string(), status: z.enum(['Started', 'Succeeded', 'Failed', 'Error']), - inputParameters: z.any(), + inputParameters: z.string().min(1), }) .openapi('baseWorkflowsSchema') @@ -177,9 +177,11 @@ export const workflowsSchema = baseWorkflowsSchema }) .openapi('WorkflowsSchema') -export const createWorkflowsSchema = baseWorkflowsSchema.openapi( - 'CreateWorkflowsSchema', -) +export const createWorkflowsSchema = baseWorkflowsSchema + .extend({ + id: z.string().optional(), + }) + .openapi('CreateWorkflowsSchema') export const updateWorkflowsSchema = baseWorkflowsSchema .partial() From 5a6118d2cbbec94c152a6e8d9a5da6c5fcdbb5a9 Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 16:44:38 +1100 Subject: [PATCH 06/15] Write a workflow, and display the table of workflows. --- apps/server/src/routes/workflows.ts | 11 ++-- apps/web/app/console/workflows/client.tsx | 79 +++++++++++++---------- packages/schemas/src/crud.ts | 4 +- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/apps/server/src/routes/workflows.ts b/apps/server/src/routes/workflows.ts index 4079f852..2d9f45c9 100644 --- a/apps/server/src/routes/workflows.ts +++ b/apps/server/src/routes/workflows.ts @@ -207,7 +207,7 @@ const app = createOpenAPIApp() const data = c.req.valid('json') /* - // TODO: Should this be included in this endpoint or a seperate endpoint? It needs to be called here to get the id before writing to the DB. + // 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 @@ -247,16 +247,19 @@ const app = createOpenAPIApp() */ // Just for testing without Argo, we will mock the response here. Remove this when Argo integration is ready. const argoWorkflow = { - id: `argo-${Date.now()}`, - name: `workflow-${Date.now()}`, + id: `id-argo-${Date.now()}`, + name: `name-argo-${Date.now()}`, } // Insert workflow record using Argo response const workflowRecord = { - ...data, + // ...data, + // Override the values while developing id: argoWorkflow.id, name: argoWorkflow.name, userId, + status: 'Started', + inputParameters: { test: 'value' }, } const [newworkflows] = await db .insert(workflows) diff --git a/apps/web/app/console/workflows/client.tsx b/apps/web/app/console/workflows/client.tsx index 06867814..d280cbdb 100644 --- a/apps/web/app/console/workflows/client.tsx +++ b/apps/web/app/console/workflows/client.tsx @@ -15,9 +15,50 @@ import Pagination from '~/components/table/pagination' import CrudFormDialog from '../../../components/form/crud-form-dialog' import BaseCrudTable from '../../../components/table/crud-table' import { WorkflowsButton } from './_components/workflows-button' -import { useAllWorkflows, useCreateWorkflows, useWorkflowsLink } from './_hooks' +import { + useAllWorkflows, + useCreateWorkflows, + useWorkflowsLink, + WorkflowsListItem, +} from './_hooks' import { createWorkflowsSchema } from '@repo/schemas/crud' import { SearchInput } from '../../../components/table/search-input' +import { ColumnDef, createColumnHelper } from '@tanstack/react-table' + +const columnHelper = createColumnHelper() + +const columns = [ + 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.inputParameters, { + id: 'inputParameters', + header: () => Input Parameters, + cell: (info) => { + const value = info.getValue() + if (!value) return null + return {JSON.stringify(value)} + }, + 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, + }), +] as ColumnDef[] const WorkflowsFeature = () => { const { @@ -49,39 +90,8 @@ const WorkflowsFeature = () => { buttonText="Add Workflows" entityName="Workflows" entityNamePlural="workflows sets" - hiddenFields={[ - 'description', - 'metadata', - 'sourceUrl', - 'sourceMetadataUrl', - ]} + hiddenFields={['id', 'name', 'description', 'metadata']} > - {/* ( - - User ID - - - - - - )} - /> */} - ( - - Status - - - - - - )} - /> { /> d !== 'description')} + extraColumns={columns} title="Workflows" itemLink={workflowsLink} itemButton={(workflows) => } diff --git a/packages/schemas/src/crud.ts b/packages/schemas/src/crud.ts index 48284ec2..8b9fe397 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -178,9 +178,7 @@ export const workflowsSchema = baseWorkflowsSchema .openapi('WorkflowsSchema') export const createWorkflowsSchema = baseWorkflowsSchema - .extend({ - id: z.string().optional(), - }) + .partial() .openapi('CreateWorkflowsSchema') export const updateWorkflowsSchema = baseWorkflowsSchema From e6d9b3d81b301efb3acb0bf89f0dbeb82d7ad513 Mon Sep 17 00:00:00 2001 From: willjnz Date: Thu, 12 Feb 2026 17:05:57 +1100 Subject: [PATCH 07/15] Add updated_at to workflows table. Write input parameters (like sourceUrl) to workflows.inputParameters field. --- apps/server/drizzle/0026_mature_spirit.sql | 1 + apps/server/drizzle/meta/0026_snapshot.json | 3606 +++++++++++++++++++ apps/server/drizzle/meta/_journal.json | 7 + apps/server/src/routes/workflows.ts | 6 +- apps/server/src/schemas/db.ts | 3 + apps/web/app/console/workflows/client.tsx | 17 +- packages/schemas/src/crud.ts | 7 +- 7 files changed, 3641 insertions(+), 6 deletions(-) create mode 100644 apps/server/drizzle/0026_mature_spirit.sql create mode 100644 apps/server/drizzle/meta/0026_snapshot.json diff --git a/apps/server/drizzle/0026_mature_spirit.sql b/apps/server/drizzle/0026_mature_spirit.sql new file mode 100644 index 00000000..51d939a2 --- /dev/null +++ b/apps/server/drizzle/0026_mature_spirit.sql @@ -0,0 +1 @@ +ALTER TABLE "workflows" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL; \ 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..a82b4509 --- /dev/null +++ b/apps/server/drizzle/meta/0026_snapshot.json @@ -0,0 +1,3606 @@ +{ + "id": "3afaafa6-b6f1-429b-88cc-1c99d3ef38ce", + "prevId": "fe4fae5c-78c2-49a5-9cd5-5e08404d6984", + "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": {}, + "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_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_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_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_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_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_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_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_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_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": {}, + "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 + }, + "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 98c8a894..71292382 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1770852904769, "tag": "0025_tranquil_cargill", "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1770876017609, + "tag": "0026_mature_spirit", + "breakpoints": true } ] } diff --git a/apps/server/src/routes/workflows.ts b/apps/server/src/routes/workflows.ts index 2d9f45c9..cbfe65b2 100644 --- a/apps/server/src/routes/workflows.ts +++ b/apps/server/src/routes/workflows.ts @@ -40,6 +40,7 @@ const workflowsQuery = (userId: string, id?: string) => status: true, inputParameters: true, createdAt: true, + updatedAt: true, completedAt: true, }, where: (workflows, { eq, and }) => @@ -240,7 +241,7 @@ const app = createOpenAPIApp() // Submit workflow to Argo let argoWorkflow try { - argoWorkflow = await submitToArgoWorkflows(data.inputParameters) + argoWorkflow = await submitToArgoWorkflows(data) } catch (err) { throw err } @@ -253,13 +254,12 @@ const app = createOpenAPIApp() // Insert workflow record using Argo response const workflowRecord = { - // ...data, // Override the values while developing id: argoWorkflow.id, name: argoWorkflow.name, userId, status: 'Started', - inputParameters: { test: 'value' }, + inputParameters: data, } const [newworkflows] = await db .insert(workflows) diff --git a/apps/server/src/schemas/db.ts b/apps/server/src/schemas/db.ts index 17870716..ecdbcd05 100644 --- a/apps/server/src/schemas/db.ts +++ b/apps/server/src/schemas/db.ts @@ -577,6 +577,9 @@ export const workflows = pgTable( createdAt: timestamp('created_at', { withTimezone: false }) .defaultNow() .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: false }) + .defaultNow() + .notNull(), completedAt: timestamp('completed_at', { withTimezone: false }), }, (table) => [ diff --git a/apps/web/app/console/workflows/client.tsx b/apps/web/app/console/workflows/client.tsx index d280cbdb..6a04a0f5 100644 --- a/apps/web/app/console/workflows/client.tsx +++ b/apps/web/app/console/workflows/client.tsx @@ -94,10 +94,23 @@ const WorkflowsFeature = () => { > ( - Input Parameters + Source URL + + + + + + )} + /> + ( + + Source Metadata URL diff --git a/packages/schemas/src/crud.ts b/packages/schemas/src/crud.ts index 8b9fe397..14ddb814 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -166,19 +166,24 @@ export const baseWorkflowsSchema = z name: z.string(), userId: z.string(), status: z.enum(['Started', 'Succeeded', 'Failed', 'Error']), - inputParameters: z.string().min(1), + inputParameters: z.any(), // jsonb }) .openapi('baseWorkflowsSchema') export const workflowsSchema = baseWorkflowsSchema .extend({ createdAt: z.iso.datetime().nullable(), + updatedAt: z.iso.datetime().nullable(), completedAt: z.iso.datetime().nullable(), }) .openapi('WorkflowsSchema') export const createWorkflowsSchema = baseWorkflowsSchema .partial() + .extend({ + sourceUrl: z.string().min(1), + sourceMetadataUrl: z.string().min(1), + }) .openapi('CreateWorkflowsSchema') export const updateWorkflowsSchema = baseWorkflowsSchema From 4a77d3152ddf9f5d6a22de45cb73aaf7191ca8e1 Mon Sep 17 00:00:00 2001 From: willjnz Date: Mon, 16 Feb 2026 09:49:55 +1100 Subject: [PATCH 08/15] Add relations for user-workflows --- apps/server/src/schemas/db.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/server/src/schemas/db.ts b/apps/server/src/schemas/db.ts index ecdbcd05..df1341a8 100644 --- a/apps/server/src/schemas/db.ts +++ b/apps/server/src/schemas/db.ts @@ -840,3 +840,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), +})) From 39e02dec8abf88165e3fc902e3dd9de3b4bcfb93 Mon Sep 17 00:00:00 2001 From: willjnz Date: Mon, 16 Feb 2026 10:16:37 +1100 Subject: [PATCH 09/15] Resolving Nick's feedback. Make workflow"s" singular. Remove where from baseWorkflowQuery. Fix query unpacking in parseQuery. --- apps/server/src/routes/workflows.ts | 68 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/apps/server/src/routes/workflows.ts b/apps/server/src/routes/workflows.ts index cbfe65b2..a70722f2 100644 --- a/apps/server/src/routes/workflows.ts +++ b/apps/server/src/routes/workflows.ts @@ -13,7 +13,6 @@ import { generateJsonResponse } from '../lib/response' import { workflows } from '../schemas/db' import { QueryForTable } from '../schemas/util' import { - baseWorkflowsSchema, createWorkflowsSchema, updateWorkflowsSchema, workflowsSchema, @@ -31,43 +30,40 @@ function throwIfNotAuthenticated(userId: string | undefined) { } } -const workflowsQuery = (userId: string, id?: string) => - ({ - columns: { - id: true, - name: true, - userId: true, - status: true, - inputParameters: true, - createdAt: true, - updatedAt: true, - completedAt: true, - }, - where: (workflows, { eq, and }) => - id - ? and(eq(workflows.userId, userId), eq(workflows.id, id)) - : eq(workflows.userId, userId), - }) satisfies QueryForTable<'workflows'> +const baseWorkflowQuery = { + columns: { + id: true, + name: true, + userId: true, + status: true, + inputParameters: true, + createdAt: true, + updatedAt: true, + completedAt: true, + }, +} satisfies QueryForTable<'workflows'> -const workflowsNotFoundError = () => +const workflowNotFoundError = () => new ServerError({ statusCode: 404, message: 'Failed to get workflow', description: "Workflow you're looking for is not found", }) -const fetchFullworkflows = async (userId: string, id?: string) => { +const fetchFullWorkflow = async (userId: string, id: string) => { const record = await db.query.workflows.findFirst({ - ...workflowsQuery(userId, id), + ...baseWorkflowQuery, + where: (workflows, { eq, and }) => + and(eq(workflows.userId, userId), eq(workflows.id, id)), }) return record ?? null } -const fetchFullworkflowsOrThrow = async (userId: string, id?: string) => { - const record = await fetchFullworkflows(userId, id) +const fetchFullWorkflowOrThrow = async (userId: string, id: string) => { + const record = await fetchFullWorkflow(userId, id) if (!record) { - throw workflowsNotFoundError() + throw workflowNotFoundError() } return record @@ -96,7 +92,7 @@ const app = createOpenAPIApp() z.object({ pageCount: z.number().int(), totalCount: z.number().int(), - data: z.array(baseWorkflowsSchema), + data: z.array(workflowsSchema), }), ), }, @@ -110,7 +106,7 @@ const app = createOpenAPIApp() async (c) => { const userId = c.get('user')?.id throwIfNotAuthenticated(userId) - const { pageCount, totalCount } = await parseQuery( + const { pageCount, totalCount, ...query } = await parseQuery( workflows, c.req.valid('query'), { @@ -120,7 +116,9 @@ const app = createOpenAPIApp() ) const data = await db.query.workflows.findMany({ - ...workflowsQuery(userId), + ...baseWorkflowQuery, + ...query, + where: and(eq(workflows.userId, userId), query.where), }) return generateJsonResponse( @@ -136,7 +134,7 @@ const app = createOpenAPIApp() ) .openapi( createRoute({ - description: 'Get a single workflow.', + description: 'Get a single workflow by ID.', method: 'get', path: '/:id', middleware: [authMiddleware({ permission: 'read:workflows' })], @@ -162,7 +160,7 @@ const app = createOpenAPIApp() const userId = c.get('user')?.id throwIfNotAuthenticated(userId) const { id } = c.req.valid('param') - const record = await fetchFullworkflowsOrThrow(userId, id) + const record = await fetchFullWorkflowOrThrow(userId, id) return generateJsonResponse(c, record, 200) }, @@ -274,7 +272,7 @@ const app = createOpenAPIApp() }) } - const record = await fetchFullworkflowsOrThrow( + const record = await fetchFullWorkflowOrThrow( newworkflows.userId, newworkflows.id, ) @@ -309,7 +307,7 @@ const app = createOpenAPIApp() description: 'Successfully updated a workflow.', content: { 'application/json': { - schema: createResponseSchema(z.any()), + schema: createResponseSchema(workflowsSchema), }, }, }, @@ -331,10 +329,10 @@ const app = createOpenAPIApp() .returning() if (!record) { - throw workflowsNotFoundError() + throw workflowNotFoundError() } - const fullRecord = await fetchFullworkflowsOrThrow(userId, record.id) + const fullRecord = await fetchFullWorkflowOrThrow(userId, record.id) return generateJsonResponse(c, fullRecord, 200, 'Workflow updated') }, @@ -358,7 +356,7 @@ const app = createOpenAPIApp() description: 'Successfully deleted a workflow.', content: { 'application/json': { - schema: createResponseSchema(z.any()), + schema: createResponseSchema(workflowsSchema), }, }, }, @@ -372,7 +370,7 @@ const app = createOpenAPIApp() const userId = c.get('user')?.id throwIfNotAuthenticated(userId) const { id } = c.req.valid('param') - const record = await fetchFullworkflowsOrThrow(userId, id) + const record = await fetchFullWorkflowOrThrow(userId, id) await db .delete(workflows) From 73735ebe4f32e01f081b482dedee5e0cf1cae83e Mon Sep 17 00:00:00 2001 From: willjnz Date: Mon, 16 Feb 2026 11:10:38 +1100 Subject: [PATCH 10/15] Rename workflows to workflow. Add workflow styles. --- apps/server/src/app.ts | 4 +- .../src/routes/{workflows.ts => workflow.ts} | 34 +-- apps/web/app/console/layout.tsx | 2 +- .../console/workflow/[workflowsId]/client.tsx | 73 ++++++ .../[workflowsId]/page.tsx | 0 .../_components/breadcrumbs.tsx | 14 +- .../workflow/_components/workflow-button.tsx | 30 +++ apps/web/app/console/workflow/_hooks.ts | 212 +++++++++++++++++ .../{workflows => workflow}/client.tsx | 39 ++-- .../{workflows => workflow}/layout.tsx | 8 +- .../console/{workflows => workflow}/page.tsx | 0 .../workflows/[workflowsId]/client.tsx | 48 ---- .../_components/workflows-button.tsx | 30 --- .../_components/workflows-select.tsx | 49 ---- apps/web/app/console/workflows/_hooks.ts | 219 ------------------ apps/web/lib/paths.ts | 2 +- packages/schemas/src/crud.ts | 18 +- packages/ui/src/components/ui/badge.tsx | 2 + packages/ui/src/globals.css | 3 + packages/ui/tailwind.config.ts | 4 + 20 files changed, 384 insertions(+), 407 deletions(-) rename apps/server/src/routes/{workflows.ts => workflow.ts} (93%) create mode 100644 apps/web/app/console/workflow/[workflowsId]/client.tsx rename apps/web/app/console/{workflows => workflow}/[workflowsId]/page.tsx (100%) rename apps/web/app/console/{workflows => workflow}/_components/breadcrumbs.tsx (76%) create mode 100644 apps/web/app/console/workflow/_components/workflow-button.tsx create mode 100644 apps/web/app/console/workflow/_hooks.ts rename apps/web/app/console/{workflows => workflow}/client.tsx (81%) rename apps/web/app/console/{workflows => workflow}/layout.tsx (51%) rename apps/web/app/console/{workflows => workflow}/page.tsx (100%) delete mode 100644 apps/web/app/console/workflows/[workflowsId]/client.tsx delete mode 100644 apps/web/app/console/workflows/_components/workflows-button.tsx delete mode 100644 apps/web/app/console/workflows/_components/workflows-select.tsx delete mode 100644 apps/web/app/console/workflows/_hooks.ts diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index bb4d483d..193e42d9 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -28,7 +28,7 @@ import indicator from './routes/indicator' import indicatorCategory from './routes/indicatorCategory' import report from './routes/report' import dashboard from './routes/dashboard' -import workflows from './routes/workflows' +import workflow from './routes/workflow' const isProduction = env.NODE_ENV === 'production' @@ -83,7 +83,7 @@ const v0ApiRoutes = app .route('/indicator-category', indicatorCategory) .route('/report', report) .route('/dashboard', dashboard) - .route('/workflows', workflows) + .route('/workflow', workflow) v0ApiRoutes.openAPIRegistry.registerComponent('securitySchemes', 'ApiKeyAuth', { type: 'apiKey', diff --git a/apps/server/src/routes/workflows.ts b/apps/server/src/routes/workflow.ts similarity index 93% rename from apps/server/src/routes/workflows.ts rename to apps/server/src/routes/workflow.ts index a70722f2..98fa3bba 100644 --- a/apps/server/src/routes/workflows.ts +++ b/apps/server/src/routes/workflow.ts @@ -13,10 +13,10 @@ import { generateJsonResponse } from '../lib/response' import { workflows } from '../schemas/db' import { QueryForTable } from '../schemas/util' import { - createWorkflowsSchema, - updateWorkflowsSchema, - workflowsSchema, - workflowsQuerySchema, + createWorkflowSchema, + updateWorkflowSchema, + workflowSchema, + workflowQuerySchema, } from '@repo/schemas/crud' import { parseQuery } from '~/utils/query' @@ -81,7 +81,7 @@ const app = createOpenAPIApp() }), ], request: { - query: workflowsQuerySchema, + query: workflowQuerySchema, }, responses: { 200: { @@ -92,7 +92,7 @@ const app = createOpenAPIApp() z.object({ pageCount: z.number().int(), totalCount: z.number().int(), - data: z.array(workflowsSchema), + data: z.array(workflowSchema), }), ), }, @@ -146,7 +146,7 @@ const app = createOpenAPIApp() description: 'Successfully retrieved a workflow.', content: { 'application/json': { - schema: createResponseSchema(workflowsSchema), + schema: createResponseSchema(workflowSchema), }, }, }, @@ -181,7 +181,7 @@ const app = createOpenAPIApp() required: true, content: { 'application/json': { - schema: createWorkflowsSchema, + schema: createWorkflowSchema, }, }, }, @@ -191,7 +191,7 @@ const app = createOpenAPIApp() description: 'Successfully created a workflow.', content: { 'application/json': { - schema: createResponseSchema(workflowsSchema), + schema: createResponseSchema(workflowSchema), }, }, }, @@ -259,22 +259,22 @@ const app = createOpenAPIApp() status: 'Started', inputParameters: data, } - const [newworkflows] = await db + const [newWorkflow] = await db .insert(workflows) .values(workflowRecord) .returning() - if (!newworkflows) { + if (!newWorkflow) { throw new ServerError({ statusCode: 500, - message: 'Failed to create workflows', + message: 'Failed to create workflow', description: 'Workflow insert did not return a record', }) } const record = await fetchFullWorkflowOrThrow( - newworkflows.userId, - newworkflows.id, + newWorkflow.userId, + newWorkflow.id, ) return generateJsonResponse(c, record, 201, 'Workflow created') @@ -297,7 +297,7 @@ const app = createOpenAPIApp() required: true, content: { 'application/json': { - schema: updateWorkflowsSchema, + schema: updateWorkflowSchema, }, }, }, @@ -307,7 +307,7 @@ const app = createOpenAPIApp() description: 'Successfully updated a workflow.', content: { 'application/json': { - schema: createResponseSchema(workflowsSchema), + schema: createResponseSchema(workflowSchema), }, }, }, @@ -356,7 +356,7 @@ const app = createOpenAPIApp() description: 'Successfully deleted a workflow.', content: { 'application/json': { - schema: createResponseSchema(workflowsSchema), + schema: createResponseSchema(workflowSchema), }, }, }, diff --git a/apps/web/app/console/layout.tsx b/apps/web/app/console/layout.tsx index ae91f0a5..ac394872 100644 --- a/apps/web/app/console/layout.tsx +++ b/apps/web/app/console/layout.tsx @@ -23,7 +23,7 @@ const SIDEBAR_CONFIG = [ }, { text: 'Workflows', - icon: , + icon: , href: WORKFLOWS_BASE_PATH, roles: ['admin', 'user'], }, 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..fef07062 --- /dev/null +++ b/apps/web/app/console/workflow/[workflowsId]/client.tsx @@ -0,0 +1,73 @@ +'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 { 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' + +const WorkflowDetails = () => { + const { data: workflow } = useWorkflow() + const updateWorkflow = useUpdateWorkflow() + const deleteWorkflow = useDeleteWorkflow(undefined, WORKFLOWS_BASE_PATH) + + const form = useForm({ + resolver: zodResolver(updateWorkflowSchema), + }) + + useEffect(() => { + if (workflow) { + form.reset(workflow) + } + }, [workflow, form]) + + console.log('workflow', workflow) + + return ( +
+
+
+
+ {workflow && } +
+
+
+ + ( + + Input Parameters + + + + + + )} + /> + +
+ ) +} + +export default WorkflowDetails diff --git a/apps/web/app/console/workflows/[workflowsId]/page.tsx b/apps/web/app/console/workflow/[workflowsId]/page.tsx similarity index 100% rename from apps/web/app/console/workflows/[workflowsId]/page.tsx rename to apps/web/app/console/workflow/[workflowsId]/page.tsx diff --git a/apps/web/app/console/workflows/_components/breadcrumbs.tsx b/apps/web/app/console/workflow/_components/breadcrumbs.tsx similarity index 76% rename from apps/web/app/console/workflows/_components/breadcrumbs.tsx rename to apps/web/app/console/workflow/_components/breadcrumbs.tsx index fbb6f6ce..dd6ddae6 100644 --- a/apps/web/app/console/workflows/_components/breadcrumbs.tsx +++ b/apps/web/app/console/workflow/_components/breadcrumbs.tsx @@ -9,15 +9,15 @@ import { import { usePathname } from 'next/navigation' import Link from '../../../../components/link' import { WORKFLOWS_BASE_PATH } from '../../../../lib/paths' -import { useWorkflows } from '../_hooks' -import { WorkflowsButton } from './workflows-button' +import { useWorkflow } from '../_hooks' +import { WorkflowButton } from './workflow-button' -export const WorkflowsBreadcrumbs = () => { +export const WorkflowBreadcrumbs = () => { const pathname = usePathname() - const { data: workflowsFromUrl } = useWorkflows() + const { data: workflowFromUrl } = useWorkflow() - const workflows = workflowsFromUrl + const workflow = workflowFromUrl return ( @@ -33,12 +33,12 @@ export const WorkflowsBreadcrumbs = () => { Workflows - {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/workflows/client.tsx b/apps/web/app/console/workflow/client.tsx similarity index 81% rename from apps/web/app/console/workflows/client.tsx rename to apps/web/app/console/workflow/client.tsx index 6a04a0f5..69c572c7 100644 --- a/apps/web/app/console/workflows/client.tsx +++ b/apps/web/app/console/workflow/client.tsx @@ -14,19 +14,18 @@ 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 { WorkflowsButton } from './_components/workflows-button' +import { WorkflowButton } from './_components/workflow-button' import { useAllWorkflows, - useCreateWorkflows, - useWorkflowsLink, - WorkflowsListItem, + useCreateWorkflow, + useWorkflowLink, + WorkflowListItem, } from './_hooks' -import { createWorkflowsSchema } from '@repo/schemas/crud' +import { createWorkflowSchema } from '@repo/schemas/crud' import { SearchInput } from '../../../components/table/search-input' import { ColumnDef, createColumnHelper } from '@tanstack/react-table' -const columnHelper = createColumnHelper() - +const columnHelper = createColumnHelper() const columns = [ columnHelper.accessor((row) => row.status, { id: 'status', @@ -44,7 +43,7 @@ const columns = [ cell: (info) => { const value = info.getValue() if (!value) return null - return {JSON.stringify(value)} + return
{JSON.stringify(value, null, 2)}
}, size: 120, }), @@ -58,9 +57,9 @@ const columns = [ }, size: 120, }), -] as ColumnDef[] +] as ColumnDef[] -const WorkflowsFeature = () => { +const WorkflowFeature = () => { const { data, query, @@ -69,15 +68,15 @@ const WorkflowsFeature = () => { hasNextPage, isFetchingNextPage, } = useAllWorkflows(undefined, true) - const createWorkflows = useCreateWorkflows() - const workflowsLink = useWorkflowsLink() + const createWorkflow = useCreateWorkflow() + const workflowLink = useWorkflowLink() const baseColumns = useMemo(() => { return ['description', 'createdAt', 'updatedAt'] as const }, []) const form = useForm({ - resolver: zodResolver(createWorkflowsSchema), + resolver: zodResolver(createWorkflowSchema), }) return ( @@ -86,10 +85,10 @@ const WorkflowsFeature = () => {

Workflows

{ baseColumns={baseColumns.filter((d) => d !== 'description')} extraColumns={columns} title="Workflows" - itemLink={workflowsLink} - itemButton={(workflows) => } + itemLink={workflowLink} + itemButton={(workflow) => } query={query} onSortChange={setSearchParams} /> @@ -147,4 +146,4 @@ const WorkflowsFeature = () => { ) } -export default WorkflowsFeature +export default WorkflowFeature diff --git a/apps/web/app/console/workflows/layout.tsx b/apps/web/app/console/workflow/layout.tsx similarity index 51% rename from apps/web/app/console/workflows/layout.tsx rename to apps/web/app/console/workflow/layout.tsx index 9960bee4..cb723b84 100644 --- a/apps/web/app/console/workflows/layout.tsx +++ b/apps/web/app/console/workflow/layout.tsx @@ -1,15 +1,15 @@ import React from 'react' import DetailLayout from '../../../components/detail-layout' -import { WorkflowsBreadcrumbs } from './_components/breadcrumbs' +import { WorkflowBreadcrumbs } from './_components/breadcrumbs' -const WorkflowsLayout: React.FC<{ +const WorkflowLayout: React.FC<{ children?: React.ReactNode }> = async ({ children }) => { return ( - }> + }> {children} ) } -export default WorkflowsLayout +export default WorkflowLayout diff --git a/apps/web/app/console/workflows/page.tsx b/apps/web/app/console/workflow/page.tsx similarity index 100% rename from apps/web/app/console/workflows/page.tsx rename to apps/web/app/console/workflow/page.tsx diff --git a/apps/web/app/console/workflows/[workflowsId]/client.tsx b/apps/web/app/console/workflows/[workflowsId]/client.tsx deleted file mode 100644 index c33257eb..00000000 --- a/apps/web/app/console/workflows/[workflowsId]/client.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { updateWorkflowsSchema } from '@repo/schemas/crud' -import { useEffect } from 'react' -import { useForm } from 'react-hook-form' -import { CrudForm } from '../../../../components/form/crud-form' -import { WORKFLOWS_BASE_PATH } from '../../../../lib/paths' -import { SourcesCard } from '../../_components/sources-card' -import { useDeleteWorkflows, useWorkflows, useUpdateWorkflows } from '../_hooks' - -const WorkflowsDetails = () => { - const { data: workflows } = useWorkflows() - const updateWorkflows = useUpdateWorkflows() - const deleteWorkflows = useDeleteWorkflows(undefined, WORKFLOWS_BASE_PATH) - - const form = useForm({ - resolver: zodResolver(updateWorkflowsSchema), - }) - - useEffect(() => { - if (workflows) { - form.reset(workflows) - } - }, [workflows, form]) - - return ( -
-
-
-
- {workflows && } -
-
-
- -
- ) -} - -export default WorkflowsDetails diff --git a/apps/web/app/console/workflows/_components/workflows-button.tsx b/apps/web/app/console/workflows/_components/workflows-button.tsx deleted file mode 100644 index b924bde1..00000000 --- a/apps/web/app/console/workflows/_components/workflows-button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { BadgeLink } from '../../../../components/badge-link' -import { WorkflowsLinkParams, useWorkflowsLink } from '../_hooks' - -export const WorkflowsButtons = ({ - workflowsSets, -}: { - workflowsSets: WorkflowsLinkParams[] | undefined -}) => { - return ( -
- {workflowsSets?.map((workflows) => ( - - ))} -
- ) -} - -export const WorkflowsButton = ({ - workflows, -}: { - workflows: WorkflowsLinkParams -}) => { - const workflowsLink = useWorkflowsLink() - - return ( - - {workflows.name} - - ) -} diff --git a/apps/web/app/console/workflows/_components/workflows-select.tsx b/apps/web/app/console/workflows/_components/workflows-select.tsx deleted file mode 100644 index 6befae2d..00000000 --- a/apps/web/app/console/workflows/_components/workflows-select.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { FieldGroup } from '../../../../components/form/action' -import { SelectWithSearch } from '@repo/ui/components/ui/select-with-search' -import { WorkflowsListItem, useAllWorkflows, useWorkflows } from '../_hooks' - -export const WorkflowsSelect = ({ - value, - onChange, - disabled, - isClearable = true, -}: { - value: string | null | undefined - onChange: (workflows: WorkflowsListItem | null) => void - disabled?: boolean - isClearable?: boolean -}) => { - const { - data: allWorkflows, - setSearchParams, - fetchNextPage, - hasNextPage, - isLoading: isLoadingWorkflows, - isFetchingNextPage, - } = useAllWorkflows() - - const { data: selectedWorkflows } = useWorkflows(value ?? undefined) - - return ( - - { - setSearchParams({ search }) - }} - onChange={(nextValue) => { - onChange(nextValue) - }} - isDisabled={disabled} - isLoading={isLoadingWorkflows || isFetchingNextPage} - onMenuScrollToBottom={() => { - if (hasNextPage) { - fetchNextPage() - } - }} - isClearable={isClearable} - /> - - ) -} diff --git a/apps/web/app/console/workflows/_hooks.ts b/apps/web/app/console/workflows/_hooks.ts deleted file mode 100644 index a34ff353..00000000 --- a/apps/web/app/console/workflows/_hooks.ts +++ /dev/null @@ -1,219 +0,0 @@ -'use client' - -import { workflowsQuerySchema } 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 WorkflowsListResponse = NonNullable< - InferResponseType['data'] -> -export type WorkflowsListItem = WorkflowsListResponse['data'][0] -export type WorkflowsDetail = NonNullable< - InferResponseType< - Client['api']['v0']['workflows'][':id']['$get'], - 200 - >['data'] -> - -export type UpdateWorkflowsPayload = NonNullable< - InferRequestType['json'] -> - -export type CreateWorkflowsPayload = NonNullable< - InferRequestType['json'] -> - -const workflowsParamsSchema = z.object({ - workflowsId: z.string().optional(), -}) - -export const workflowsQueryKeys = { - all: ['workflows'] as const, - list: (query: z.infer | undefined) => - [...workflowsQueryKeys.all, 'list', { query }] as const, - detail: (workflowsId: string | undefined) => - [...workflowsQueryKeys.all, 'detail', workflowsId] as const, -} - -export const useWorkflowsParams = (_workflowsId?: string) => { - const params = useParams() - const { workflowsId } = workflowsParamsSchema.parse(params) - - return { - workflowsId: _workflowsId ?? workflowsId, - } -} - -export const useAllWorkflows = ( - _query?: z.infer, - useSearchParams?: boolean, -) => { - const client = useApiClient() - const { query, setSearchParams } = useQueryWithSearchParams( - workflowsQuerySchema, - _query, - useSearchParams, - ) - - const queryResult = useInfiniteQuery({ - queryKey: workflowsQueryKeys.list(query), - queryFn: async ({ pageParam = 1 }) => { - const res = client.api.v0.workflows.$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 useWorkflows = ( - _workflowsId?: string, - enabled: boolean = true, -) => { - const { workflowsId } = useWorkflowsParams(_workflowsId) - const client = useApiClient() - return useQuery({ - queryKey: workflowsQueryKeys.detail(workflowsId), - queryFn: async () => { - if (!workflowsId) return null - const res = client.api.v0.workflows[':id'].$get({ - param: { - id: workflowsId, - }, - }) - - const json = await unwrapResponse(res) - - return json.data - }, - enabled: enabled ?? !!workflowsId, - }) -} - -export const useCreateWorkflows = () => { - const queryClient = useQueryClient() - const client = useApiClient() - return useMutation({ - mutationFn: async (data: CreateWorkflowsPayload) => { - const res = client.api.v0.workflows.$post({ - json: data, - }) - await unwrapResponse(res, 201) - }, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: workflowsQueryKeys.all, - }) - }, - }) -} - -export const useUpdateWorkflows = (_workflowsId?: string) => { - const { workflowsId } = useWorkflowsParams(_workflowsId) - const queryClient = useQueryClient() - const client = useApiClient() - return useMutation({ - mutationFn: async (payload: UpdateWorkflowsPayload) => { - if (!workflowsId) return - const res = client.api.v0.workflows[':id'].$patch({ - param: { id: workflowsId }, - json: payload, - }) - return await unwrapResponse(res) - }, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: workflowsQueryKeys.all, - }) - }, - }) -} - -export const useDeleteWorkflows = ( - _workflowsId?: string, - redirect: string | null = null, -) => { - const { workflowsId } = useWorkflowsParams(_workflowsId) - const queryClient = useQueryClient() - const router = useRouter() - const client = useApiClient() - return useMutation({ - mutationFn: async () => { - if (!workflowsId) return - const res = client.api.v0.workflows[':id'].$delete({ - param: { - id: workflowsId, - }, - }) - - return await unwrapResponse(res) - }, - onSuccess: (response) => { - queryClient.removeQueries({ - queryKey: workflowsQueryKeys.detail(response?.data?.id), - }) - - queryClient.invalidateQueries({ - queryKey: workflowsQueryKeys.all, - }) - - if (redirect) { - router.push(redirect) - } - }, - }) -} - -export type WorkflowsLinkParams = Pick - -export const useAllWorkflowsLink = () => - useCallback( - (query?: z.infer) => - `${WORKFLOWS_BASE_PATH}?${getSearchParams(query ?? {})}`, - [], - ) - -export const useWorkflowsLink = () => - useCallback( - (workflows: WorkflowsLinkParams) => - `${WORKFLOWS_BASE_PATH}/${workflows.id}`, - [], - ) diff --git a/apps/web/lib/paths.ts b/apps/web/lib/paths.ts index f74cf341..968ae783 100644 --- a/apps/web/lib/paths.ts +++ b/apps/web/lib/paths.ts @@ -1,7 +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/workflows' +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 14ddb814..f291ec74 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -160,7 +160,7 @@ export const updateIndicatorCategorySchema = baseUpdateResourceSchema.extend({ }) /* WORKFLOW RESOURCE SCHEMAS */ -export const baseWorkflowsSchema = z +export const baseWorkflowSchema = z .object({ id: z.string().min(1), name: z.string(), @@ -168,32 +168,32 @@ export const baseWorkflowsSchema = z status: z.enum(['Started', 'Succeeded', 'Failed', 'Error']), inputParameters: z.any(), // jsonb }) - .openapi('baseWorkflowsSchema') + .openapi('baseWorkflowSchema') -export const workflowsSchema = baseWorkflowsSchema +export const workflowSchema = baseWorkflowSchema .extend({ createdAt: z.iso.datetime().nullable(), updatedAt: z.iso.datetime().nullable(), completedAt: z.iso.datetime().nullable(), }) - .openapi('WorkflowsSchema') + .openapi('WorkflowSchema') -export const createWorkflowsSchema = baseWorkflowsSchema +export const createWorkflowSchema = baseWorkflowSchema .partial() .extend({ sourceUrl: z.string().min(1), sourceMetadataUrl: z.string().min(1), }) - .openapi('CreateWorkflowsSchema') + .openapi('CreateWorkflowSchema') -export const updateWorkflowsSchema = baseWorkflowsSchema +export const updateWorkflowSchema = baseWorkflowSchema .partial() .extend({ completedAt: z.iso.datetime().nullable().optional(), }) - .openapi('UpdateWorkflowsSchema') + .openapi('UpdateWorkflowSchema') -export const workflowsQuerySchema = baseQuerySchema +export const workflowQuerySchema = baseQuerySchema /* DATASET RESOURCE SCHEMAS */ export const baseDatasetRunSchema = baseRunResourceSchema 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)', From 6dcece23af2c35eae5c74e0818b40507aa1bb63f Mon Sep 17 00:00:00 2001 From: willjnz Date: Tue, 17 Feb 2026 10:49:47 +1100 Subject: [PATCH 11/15] Show workflow id in breadcrumbs and detailed single workflow page. --- .../console/workflow/[workflowsId]/client.tsx | 74 +++++++++++++++++-- .../workflow/_components/breadcrumbs.tsx | 4 +- apps/web/app/console/workflow/client.tsx | 26 +++---- 3 files changed, 82 insertions(+), 22 deletions(-) diff --git a/apps/web/app/console/workflow/[workflowsId]/client.tsx b/apps/web/app/console/workflow/[workflowsId]/client.tsx index fef07062..4bfa39aa 100644 --- a/apps/web/app/console/workflow/[workflowsId]/client.tsx +++ b/apps/web/app/console/workflow/[workflowsId]/client.tsx @@ -4,6 +4,7 @@ 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' @@ -17,10 +18,23 @@ import { } from '@repo/ui/components/ui/form' import { Input } from '@repo/ui/components/ui/input' +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 { data: workflow } = useWorkflow() - const updateWorkflow = useUpdateWorkflow() - const deleteWorkflow = useDeleteWorkflow(undefined, WORKFLOWS_BASE_PATH) + 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), @@ -32,8 +46,6 @@ const WorkflowDetails = () => { } }, [workflow, form]) - console.log('workflow', workflow) - return (
@@ -50,21 +62,67 @@ const WorkflowDetails = () => { entityName="Workflow" entityNamePlural="Workflows" successMessage="Updated Workflow" - readOnlyFields={['inputParameters']} + readOnlyFields={['id', 'name']} + hiddenFields={['description', 'metadata']} > ( Input Parameters - + { + let val = e.target.value + try { + val = JSON.parse(val) + } catch {} + field.onChange(val) + }} + /> )} /> + {['status', 'createdAt', 'updatedAt', 'completedAt'].map( + (fieldName) => ( + ( + + {camelCaseToTitleCase(fieldName)} + + + + + + )} + /> + ), + )}
) diff --git a/apps/web/app/console/workflow/_components/breadcrumbs.tsx b/apps/web/app/console/workflow/_components/breadcrumbs.tsx index dd6ddae6..d30f83fe 100644 --- a/apps/web/app/console/workflow/_components/breadcrumbs.tsx +++ b/apps/web/app/console/workflow/_components/breadcrumbs.tsx @@ -14,8 +14,10 @@ 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() + const { data: workflowFromUrl } = useWorkflow(maybeWorkflowId) const workflow = workflowFromUrl diff --git a/apps/web/app/console/workflow/client.tsx b/apps/web/app/console/workflow/client.tsx index 69c572c7..30fbe66e 100644 --- a/apps/web/app/console/workflow/client.tsx +++ b/apps/web/app/console/workflow/client.tsx @@ -27,6 +27,16 @@ import { ColumnDef, createColumnHelper } from '@tanstack/react-table' const columnHelper = createColumnHelper() const columns = [ + 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, @@ -47,16 +57,6 @@ const columns = [ }, 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, - }), ] as ColumnDef[] const WorkflowFeature = () => { @@ -72,7 +72,7 @@ const WorkflowFeature = () => { const workflowLink = useWorkflowLink() const baseColumns = useMemo(() => { - return ['description', 'createdAt', 'updatedAt'] as const + return ['id', 'createdAt', 'updatedAt'] as const }, []) const form = useForm({ @@ -89,7 +89,7 @@ const WorkflowFeature = () => { buttonText="Add Workflow" entityName="Workflow" entityNamePlural="Workflows" - hiddenFields={['id', 'name', 'description', 'metadata']} + hiddenFields={['metadata']} > { /> d !== 'description')} + baseColumns={baseColumns} extraColumns={columns} title="Workflows" itemLink={workflowLink} From 4708189fe0260a6344f6f35469c78bdfbf185d49 Mon Sep 17 00:00:00 2001 From: willjnz Date: Tue, 17 Feb 2026 12:44:33 +1100 Subject: [PATCH 12/15] Add message to workflows table. Add workflows to seed DB script. Add duration. --- .../drizzle/0027_tiresome_miracleman.sql | 1 + apps/server/drizzle/meta/0027_snapshot.json | 3612 +++++++++++++++++ apps/server/drizzle/meta/_journal.json | 7 + apps/server/scripts/seed.ts | 53 + apps/server/src/routes/workflow.ts | 46 +- apps/server/src/schemas/db.ts | 1 + .../console/workflow/[workflowsId]/client.tsx | 63 +- apps/web/app/console/workflow/client.tsx | 16 + packages/schemas/src/crud.ts | 2 + 9 files changed, 3770 insertions(+), 31 deletions(-) create mode 100644 apps/server/drizzle/0027_tiresome_miracleman.sql create mode 100644 apps/server/drizzle/meta/0027_snapshot.json diff --git a/apps/server/drizzle/0027_tiresome_miracleman.sql b/apps/server/drizzle/0027_tiresome_miracleman.sql new file mode 100644 index 00000000..f94233a4 --- /dev/null +++ b/apps/server/drizzle/0027_tiresome_miracleman.sql @@ -0,0 +1 @@ +ALTER TABLE "workflows" ADD COLUMN "message" text; \ No newline at end of file diff --git a/apps/server/drizzle/meta/0027_snapshot.json b/apps/server/drizzle/meta/0027_snapshot.json new file mode 100644 index 00000000..ae6c8450 --- /dev/null +++ b/apps/server/drizzle/meta/0027_snapshot.json @@ -0,0 +1,3612 @@ +{ + "id": "decdac0c-0482-465c-843a-4dd86b4acb93", + "prevId": "3afaafa6-b6f1-429b-88cc-1c99d3ef38ce", + "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": {}, + "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_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_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_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_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_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_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_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_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_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": {}, + "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 71292382..20a97693 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -190,6 +190,13 @@ "when": 1770876017609, "tag": "0026_mature_spirit", "breakpoints": true + }, + { + "idx": 27, + "version": "7", + "when": 1771285905490, + "tag": "0027_tiresome_miracleman", + "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/routes/workflow.ts b/apps/server/src/routes/workflow.ts index 98fa3bba..f2c3a7bb 100644 --- a/apps/server/src/routes/workflow.ts +++ b/apps/server/src/routes/workflow.ts @@ -20,6 +20,29 @@ import { } 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({ @@ -36,6 +59,7 @@ const baseWorkflowQuery = { name: true, userId: true, status: true, + message: true, inputParameters: true, createdAt: true, updatedAt: true, @@ -121,11 +145,17 @@ const app = createOpenAPIApp() 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, + data: dataWithDuration, totalCount, }, 200, @@ -161,8 +191,15 @@ const app = createOpenAPIApp() 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, record, 200) + return generateJsonResponse(c, recordWithDuration, 200) }, ) @@ -233,6 +270,7 @@ const app = createOpenAPIApp() return { id: argoResult.metadata?.uid || argoResult.id, name: argoResult.metadata?.name || argoResult.name, + message: 'Workflow submitted to Argo successfully.', } } @@ -248,6 +286,8 @@ const app = createOpenAPIApp() 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 @@ -257,6 +297,8 @@ const app = createOpenAPIApp() name: argoWorkflow.name, userId, status: 'Started', + // message: argoWorkflow.message, + message: null, inputParameters: data, } const [newWorkflow] = await db diff --git a/apps/server/src/schemas/db.ts b/apps/server/src/schemas/db.ts index df1341a8..2b5e58ad 100644 --- a/apps/server/src/schemas/db.ts +++ b/apps/server/src/schemas/db.ts @@ -573,6 +573,7 @@ export const workflows = pgTable( .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() diff --git a/apps/web/app/console/workflow/[workflowsId]/client.tsx b/apps/web/app/console/workflow/[workflowsId]/client.tsx index 4bfa39aa..b6f5cbf1 100644 --- a/apps/web/app/console/workflow/[workflowsId]/client.tsx +++ b/apps/web/app/console/workflow/[workflowsId]/client.tsx @@ -94,35 +94,40 @@ const WorkflowDetails = () => { )} /> - {['status', 'createdAt', 'updatedAt', 'completedAt'].map( - (fieldName) => ( - ( - - {camelCaseToTitleCase(fieldName)} - - - - - - )} - /> - ), - )} + {[ + 'status', + 'message', + 'duration', + 'createdAt', + 'updatedAt', + 'completedAt', + ].map((fieldName) => ( + ( + + {camelCaseToTitleCase(fieldName)} + + + + + + )} + /> + ))}
) diff --git a/apps/web/app/console/workflow/client.tsx b/apps/web/app/console/workflow/client.tsx index 30fbe66e..98534e1d 100644 --- a/apps/web/app/console/workflow/client.tsx +++ b/apps/web/app/console/workflow/client.tsx @@ -27,6 +27,14 @@ import { ColumnDef, createColumnHelper } from '@tanstack/react-table' 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, @@ -47,6 +55,14 @@ const columns = [ }, 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, diff --git a/packages/schemas/src/crud.ts b/packages/schemas/src/crud.ts index f291ec74..8252815c 100644 --- a/packages/schemas/src/crud.ts +++ b/packages/schemas/src/crud.ts @@ -166,12 +166,14 @@ export const baseWorkflowSchema = z 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(), From 7455333fd1a580a946ea52dd7a65f7c713aae945 Mon Sep 17 00:00:00 2001 From: willjnz Date: Tue, 17 Feb 2026 14:20:52 +1100 Subject: [PATCH 13/15] Add contact support button for failed workflows. --- .../web/app/console/workflow/[workflowsId]/client.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/web/app/console/workflow/[workflowsId]/client.tsx b/apps/web/app/console/workflow/[workflowsId]/client.tsx index b6f5cbf1..170aa8f4 100644 --- a/apps/web/app/console/workflow/[workflowsId]/client.tsx +++ b/apps/web/app/console/workflow/[workflowsId]/client.tsx @@ -124,6 +124,17 @@ const WorkflowDetails = () => { /> + {fieldName === 'status' && + (field.value === 'Failed' || field.value === 'Error') && ( +

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

+ )}
)} /> From 519d98c983f6446c7a29cfc057528b831ee9e0d5 Mon Sep 17 00:00:00 2001 From: willjnz Date: Wed, 18 Feb 2026 11:30:37 +1100 Subject: [PATCH 14/15] Add status traffic light --- apps/server/src/routes/workflow.ts | 6 +++++- .../console/workflow/[workflowsId]/client.tsx | 7 +++++++ apps/web/app/console/workflow/client.tsx | 16 +++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/server/src/routes/workflow.ts b/apps/server/src/routes/workflow.ts index f2c3a7bb..d46ddb8d 100644 --- a/apps/server/src/routes/workflow.ts +++ b/apps/server/src/routes/workflow.ts @@ -299,7 +299,11 @@ const app = createOpenAPIApp() status: 'Started', // message: argoWorkflow.message, message: null, - inputParameters: data, + // inputParameters: data, + inputParameters: { + sourceUrl: data.sourceUrl, + sourceMetadataUrl: data.sourceMetadataUrl, + }, } const [newWorkflow] = await db .insert(workflows) diff --git a/apps/web/app/console/workflow/[workflowsId]/client.tsx b/apps/web/app/console/workflow/[workflowsId]/client.tsx index 170aa8f4..e0807233 100644 --- a/apps/web/app/console/workflow/[workflowsId]/client.tsx +++ b/apps/web/app/console/workflow/[workflowsId]/client.tsx @@ -17,6 +17,7 @@ import { 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 @@ -109,6 +110,12 @@ const WorkflowDetails = () => { render={({ field }) => ( {camelCaseToTitleCase(fieldName)} + {fieldName === 'status' && ( + <> + + + + )} { + 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, { @@ -51,7 +60,12 @@ const columns = [ cell: (info) => { const value = info.getValue() if (!value) return null - return {value} + return ( + + + {value} + + ) }, size: 120, }), From e15e16f017e3179bb7a76b034acf367b11e63fb2 Mon Sep 17 00:00:00 2001 From: Nick Forbes-Smith Date: Tue, 10 Mar 2026 14:12:04 +1100 Subject: [PATCH 15/15] Merge migrations from main --- ...tranquil_cargill.sql => 0026_far_tusk.sql} | 2 + apps/server/drizzle/0026_mature_spirit.sql | 1 - .../drizzle/0027_tiresome_miracleman.sql | 1 - apps/server/drizzle/meta/0025_snapshot.json | 389 +- apps/server/drizzle/meta/0026_snapshot.json | 309 +- apps/server/drizzle/meta/0027_snapshot.json | 3612 ----------------- apps/server/drizzle/meta/_journal.json | 15 +- 7 files changed, 594 insertions(+), 3735 deletions(-) rename apps/server/drizzle/{0025_tranquil_cargill.sql => 0026_far_tusk.sql} (92%) delete mode 100644 apps/server/drizzle/0026_mature_spirit.sql delete mode 100644 apps/server/drizzle/0027_tiresome_miracleman.sql delete mode 100644 apps/server/drizzle/meta/0027_snapshot.json diff --git a/apps/server/drizzle/0025_tranquil_cargill.sql b/apps/server/drizzle/0026_far_tusk.sql similarity index 92% rename from apps/server/drizzle/0025_tranquil_cargill.sql rename to apps/server/drizzle/0026_far_tusk.sql index 72d73676..8ceee9bd 100644 --- a/apps/server/drizzle/0025_tranquil_cargill.sql +++ b/apps/server/drizzle/0026_far_tusk.sql @@ -4,8 +4,10 @@ CREATE TABLE "workflows" ( "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 diff --git a/apps/server/drizzle/0026_mature_spirit.sql b/apps/server/drizzle/0026_mature_spirit.sql deleted file mode 100644 index 51d939a2..00000000 --- a/apps/server/drizzle/0026_mature_spirit.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "workflows" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL; \ No newline at end of file diff --git a/apps/server/drizzle/0027_tiresome_miracleman.sql b/apps/server/drizzle/0027_tiresome_miracleman.sql deleted file mode 100644 index f94233a4..00000000 --- a/apps/server/drizzle/0027_tiresome_miracleman.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "workflows" ADD COLUMN "message" text; \ No newline at end of file diff --git a/apps/server/drizzle/meta/0025_snapshot.json b/apps/server/drizzle/meta/0025_snapshot.json index a97dba38..48cb56d3 100644 --- a/apps/server/drizzle/meta/0025_snapshot.json +++ b/apps/server/drizzle/meta/0025_snapshot.json @@ -1,5 +1,5 @@ { - "id": "fe4fae5c-78c2-49a5-9cd5-5e08404d6984", + "id": "1840126a-72fc-4a1f-939c-fc6bfc5425f3", "prevId": "387ea224-9088-4411-987c-981ed5b0e430", "version": "7", "dialect": "postgresql", @@ -53,7 +53,31 @@ "notNull": true } }, - "indexes": {}, + "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": {}, @@ -123,6 +147,29 @@ } }, "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": [ @@ -289,6 +336,29 @@ } }, "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": [ @@ -410,6 +480,36 @@ } }, "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": [ @@ -605,6 +705,29 @@ } }, "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": [ @@ -777,6 +900,29 @@ } }, "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": [ @@ -1000,6 +1146,36 @@ } }, "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": [ @@ -1272,6 +1448,36 @@ } }, "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": [ @@ -1475,6 +1681,27 @@ } }, "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": [ @@ -2101,6 +2328,50 @@ } }, "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": [ @@ -2466,116 +2737,32 @@ "notNull": false } }, - "indexes": {}, - "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 - }, - "input_parameters": { - "name": "input_parameters", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_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", + "report_search_trgm_idx": { + "name": "report_search_trgm_idx", "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", + "expression": "name", "isExpression": false, "asc": true, - "nulls": "last" + "nulls": "last", + "opclass": "gin_trgm_ops" }, { - "expression": "user_id", + "expression": "description", "isExpression": false, "asc": true, - "nulls": "last" + "nulls": "last", + "opclass": "gin_trgm_ops" } ], "isUnique": false, "concurrently": false, - "method": "btree", + "method": "gin", "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" - } - }, + "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {}, "policies": {}, @@ -3574,16 +3761,6 @@ "month", "year" ] - }, - "public.workflow_status": { - "name": "workflow_status", - "schema": "public", - "values": [ - "Started", - "Succeeded", - "Failed", - "Error" - ] } }, "schemas": {}, diff --git a/apps/server/drizzle/meta/0026_snapshot.json b/apps/server/drizzle/meta/0026_snapshot.json index a82b4509..cf238694 100644 --- a/apps/server/drizzle/meta/0026_snapshot.json +++ b/apps/server/drizzle/meta/0026_snapshot.json @@ -1,6 +1,6 @@ { - "id": "3afaafa6-b6f1-429b-88cc-1c99d3ef38ce", - "prevId": "fe4fae5c-78c2-49a5-9cd5-5e08404d6984", + "id": "b131c05e-e5de-42f2-b243-f38727b06f99", + "prevId": "1840126a-72fc-4a1f-939c-fc6bfc5425f3", "version": "7", "dialect": "postgresql", "tables": { @@ -53,7 +53,31 @@ "notNull": true } }, - "indexes": {}, + "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": {}, @@ -123,6 +147,29 @@ } }, "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": [ @@ -289,6 +336,29 @@ } }, "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": [ @@ -410,6 +480,36 @@ } }, "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": [ @@ -605,6 +705,29 @@ } }, "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": [ @@ -777,6 +900,29 @@ } }, "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": [ @@ -1000,6 +1146,36 @@ } }, "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": [ @@ -1272,6 +1448,36 @@ } }, "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": [ @@ -1475,6 +1681,27 @@ } }, "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": [ @@ -2101,6 +2328,50 @@ } }, "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": [ @@ -2466,7 +2737,31 @@ "notNull": false } }, - "indexes": {}, + "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": {}, @@ -2503,6 +2798,12 @@ "primaryKey": false, "notNull": true }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, "input_parameters": { "name": "input_parameters", "type": "jsonb", diff --git a/apps/server/drizzle/meta/0027_snapshot.json b/apps/server/drizzle/meta/0027_snapshot.json deleted file mode 100644 index ae6c8450..00000000 --- a/apps/server/drizzle/meta/0027_snapshot.json +++ /dev/null @@ -1,3612 +0,0 @@ -{ - "id": "decdac0c-0482-465c-843a-4dd86b4acb93", - "prevId": "3afaafa6-b6f1-429b-88cc-1c99d3ef38ce", - "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": {}, - "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_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_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_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_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_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_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_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_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_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": {}, - "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 20a97693..610a8d58 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -180,22 +180,15 @@ { "idx": 25, "version": "7", - "when": 1770852904769, - "tag": "0025_tranquil_cargill", + "when": 1772420069848, + "tag": "0025_hesitant_christian_walker", "breakpoints": true }, { "idx": 26, "version": "7", - "when": 1770876017609, - "tag": "0026_mature_spirit", - "breakpoints": true - }, - { - "idx": 27, - "version": "7", - "when": 1771285905490, - "tag": "0027_tiresome_miracleman", + "when": 1773112296152, + "tag": "0026_far_tusk", "breakpoints": true } ]