From 62cd45d0c597499585aa37d7c6969c150c90edea Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 4 Dec 2025 15:06:12 -0500 Subject: [PATCH] Database schema --- backend/src/@types/knex.d.ts | 66 +++++- .../20251203002657_global-approvals.ts | 192 ++++++++++++++++++ backend/src/db/schemas/approval-policies.ts | 26 +++ .../schemas/approval-policy-step-approvers.ts | 24 +++ .../src/db/schemas/approval-policy-steps.ts | 21 ++ .../db/schemas/approval-request-approvals.ts | 23 +++ .../src/db/schemas/approval-request-grants.ts | 27 +++ ...pproval-request-step-eligible-approvers.ts | 24 +++ .../src/db/schemas/approval-request-steps.ts | 23 +++ backend/src/db/schemas/approval-requests.ts | 30 +++ backend/src/db/schemas/index.ts | 8 + backend/src/db/schemas/models.ts | 12 +- 12 files changed, 474 insertions(+), 2 deletions(-) create mode 100644 backend/src/db/migrations/20251203002657_global-approvals.ts create mode 100644 backend/src/db/schemas/approval-policies.ts create mode 100644 backend/src/db/schemas/approval-policy-step-approvers.ts create mode 100644 backend/src/db/schemas/approval-policy-steps.ts create mode 100644 backend/src/db/schemas/approval-request-approvals.ts create mode 100644 backend/src/db/schemas/approval-request-grants.ts create mode 100644 backend/src/db/schemas/approval-request-step-eligible-approvers.ts create mode 100644 backend/src/db/schemas/approval-request-steps.ts create mode 100644 backend/src/db/schemas/approval-requests.ts diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index 4bdd3849d..12e30eb38 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -571,7 +571,31 @@ import { TWebhooksUpdate, TWorkflowIntegrations, TWorkflowIntegrationsInsert, - TWorkflowIntegrationsUpdate + TWorkflowIntegrationsUpdate, + TApprovalPolicies, + TApprovalPoliciesInsert, + TApprovalPoliciesUpdate, + TApprovalPolicyStepApprovers, + TApprovalPolicyStepApproversInsert, + TApprovalPolicyStepApproversUpdate, + TApprovalPolicySteps, + TApprovalPolicyStepsInsert, + TApprovalPolicyStepsUpdate, + TApprovalRequestApprovals, + TApprovalRequestApprovalsInsert, + TApprovalRequestApprovalsUpdate, + TApprovalRequestGrants, + TApprovalRequestGrantsInsert, + TApprovalRequestGrantsUpdate, + TApprovalRequestStepEligibleApprovers, + TApprovalRequestStepEligibleApproversInsert, + TApprovalRequestStepEligibleApproversUpdate, + TApprovalRequestSteps, + TApprovalRequestStepsInsert, + TApprovalRequestStepsUpdate, + TApprovalRequests, + TApprovalRequestsInsert, + TApprovalRequestsUpdate } from "@app/db/schemas"; import { TCertificateRequests, @@ -1475,5 +1499,45 @@ declare module "knex/types/tables" { TVaultExternalMigrationConfigsInsert, TVaultExternalMigrationConfigsUpdate >; + [TableName.ApprovalPolicies]: KnexOriginal.CompositeTableType< + TApprovalPolicies, + TApprovalPoliciesInsert, + TApprovalPoliciesUpdate + >; + [TableName.ApprovalPolicyStepApprovers]: KnexOriginal.CompositeTableType< + TApprovalPolicyStepApprovers, + TApprovalPolicyStepApproversInsert, + TApprovalPolicyStepApproversUpdate + >; + [TableName.ApprovalPolicySteps]: KnexOriginal.CompositeTableType< + TApprovalPolicySteps, + TApprovalPolicyStepsInsert, + TApprovalPolicyStepsUpdate + >; + [TableName.ApprovalRequestApprovals]: KnexOriginal.CompositeTableType< + TApprovalRequestApprovals, + TApprovalRequestApprovalsInsert, + TApprovalRequestApprovalsUpdate + >; + [TableName.ApprovalRequestGrants]: KnexOriginal.CompositeTableType< + TApprovalRequestGrants, + TApprovalRequestGrantsInsert, + TApprovalRequestGrantsUpdate + >; + [TableName.ApprovalRequestStepEligibleApprovers]: KnexOriginal.CompositeTableType< + TApprovalRequestStepEligibleApprovers, + TApprovalRequestStepEligibleApproversInsert, + TApprovalRequestStepEligibleApproversUpdate + >; + [TableName.ApprovalRequestSteps]: KnexOriginal.CompositeTableType< + TApprovalRequestSteps, + TApprovalRequestStepsInsert, + TApprovalRequestStepsUpdate + >; + [TableName.ApprovalRequests]: KnexOriginal.CompositeTableType< + TApprovalRequests, + TApprovalRequestsInsert, + TApprovalRequestsUpdate + >; } } diff --git a/backend/src/db/migrations/20251203002657_global-approvals.ts b/backend/src/db/migrations/20251203002657_global-approvals.ts new file mode 100644 index 000000000..c5ceda98d --- /dev/null +++ b/backend/src/db/migrations/20251203002657_global-approvals.ts @@ -0,0 +1,192 @@ +import { Knex } from "knex"; +import { TableName } from "../schemas"; +import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasTable(TableName.ApprovalPolicies))) { + await knex.schema.createTable(TableName.ApprovalPolicies, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.string("projectId").notNullable().index(); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + + t.uuid("organizationId").notNullable().index(); + t.foreign("organizationId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); + + t.string("type").notNullable().index(); + t.string("name").notNullable(); + + t.boolean("isActive").defaultTo(true); + + t.integer("maxRequestTtlSeconds").nullable(); + + t.jsonb("conditions").notNullable(); + t.jsonb("constraints").notNullable(); + + t.timestamps(true, true, true); + }); + await createOnUpdateTrigger(knex, TableName.ApprovalPolicies); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalPolicySteps))) { + await knex.schema.createTable(TableName.ApprovalPolicySteps, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.uuid("policyId").notNullable().index(); + t.foreign("policyId").references("id").inTable(TableName.ApprovalPolicies).onDelete("CASCADE"); + + t.string("name").nullable(); + t.integer("stepNumber").notNullable(); + + t.integer("requiredApprovals").notNullable(); + t.boolean("notifyApprovers").defaultTo(false); + }); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalPolicyStepApprovers))) { + await knex.schema.createTable(TableName.ApprovalPolicyStepApprovers, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.uuid("policyStepId").notNullable().index(); + t.foreign("policyStepId").references("id").inTable(TableName.ApprovalPolicySteps).onDelete("CASCADE"); + + t.uuid("userId").nullable().index(); + t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + + t.uuid("groupId").nullable().index(); + t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE"); + + t.check('("userId" IS NOT NULL AND "groupId" IS NULL) OR ("userId" IS NULL AND "groupId" IS NOT NULL)'); + }); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalRequests))) { + await knex.schema.createTable(TableName.ApprovalRequests, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.string("projectId").notNullable().index(); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + + t.uuid("organizationId").notNullable().index(); + t.foreign("organizationId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); + + t.uuid("policyId").notNullable().index(); + t.foreign("policyId").references("id").inTable(TableName.ApprovalPolicies).onDelete("CASCADE"); + + t.uuid("requesterId").notNullable().index(); + t.foreign("requesterId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + + // To be used in the event of requester deletion + t.string("requesterName").notNullable(); + t.string("requesterEmail").notNullable(); + + t.string("type").notNullable().index(); + + t.string("status").notNullable().index(); + t.text("justification").nullable(); + t.integer("currentStep").notNullable(); + + t.jsonb("requestData").notNullable(); + + t.timestamp("expiresAt").nullable(); + t.timestamps(true, true, true); + }); + await createOnUpdateTrigger(knex, TableName.ApprovalRequests); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalRequestSteps))) { + await knex.schema.createTable(TableName.ApprovalRequestSteps, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.uuid("requestId").notNullable().index(); + t.foreign("requestId").references("id").inTable(TableName.ApprovalRequests).onDelete("CASCADE"); + + t.integer("stepNumber").notNullable(); + + t.string("name").nullable(); + t.string("status").notNullable().index(); + + t.integer("requiredApprovals").notNullable(); + + t.timestamp("startedAt").nullable(); + t.timestamp("completedAt").nullable(); + }); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalRequestStepEligibleApprovers))) { + await knex.schema.createTable(TableName.ApprovalRequestStepEligibleApprovers, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.uuid("stepId").notNullable().index(); + t.foreign("stepId").references("id").inTable(TableName.ApprovalRequestSteps).onDelete("CASCADE"); + + t.uuid("userId").nullable().index(); + t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + + t.uuid("groupId").nullable().index(); + t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE"); + + t.check('("userId" IS NOT NULL AND "groupId" IS NULL) OR ("userId" IS NULL AND "groupId" IS NOT NULL)'); + }); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalRequestApprovals))) { + await knex.schema.createTable(TableName.ApprovalRequestApprovals, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.uuid("stepId").notNullable().index(); + t.foreign("stepId").references("id").inTable(TableName.ApprovalRequestSteps).onDelete("CASCADE"); + + t.uuid("approverUserId").notNullable().index(); + t.foreign("approverUserId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + + t.string("decision").notNullable(); + t.text("comment").nullable(); + + t.timestamp("createdAt").defaultTo(knex.fn.now()); + }); + } + + if (!(await knex.schema.hasTable(TableName.ApprovalRequestGrants))) { + await knex.schema.createTable(TableName.ApprovalRequestGrants, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.string("projectId").notNullable().index(); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + + t.uuid("requestId").notNullable().index(); + t.foreign("requestId").references("id").inTable(TableName.ApprovalRequests).onDelete("CASCADE"); + + t.uuid("granteeUserId").notNullable().index(); + t.foreign("granteeUserId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + + t.uuid("revokedByUserId").nullable().index(); + t.foreign("revokedByUserId").references("id").inTable(TableName.Users).onDelete("SET NULL"); + + t.text("revocationReason").nullable(); + + t.string("status").notNullable().index(); + t.string("type").notNullable().index(); + + t.jsonb("attributes").notNullable(); + + t.timestamp("createdAt").defaultTo(knex.fn.now()); + t.timestamp("expiresAt").nullable(); + t.timestamp("revokedAt").nullable(); + }); + } +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists(TableName.ApprovalRequestGrants); + await knex.schema.dropTableIfExists(TableName.ApprovalRequestApprovals); + await knex.schema.dropTableIfExists(TableName.ApprovalRequestStepEligibleApprovers); + await knex.schema.dropTableIfExists(TableName.ApprovalRequestSteps); + await knex.schema.dropTableIfExists(TableName.ApprovalRequests); + await knex.schema.dropTableIfExists(TableName.ApprovalPolicyStepApprovers); + await knex.schema.dropTableIfExists(TableName.ApprovalPolicySteps); + await knex.schema.dropTableIfExists(TableName.ApprovalPolicies); + + await dropOnUpdateTrigger(knex, TableName.ApprovalRequests); + await dropOnUpdateTrigger(knex, TableName.ApprovalPolicies); +} diff --git a/backend/src/db/schemas/approval-policies.ts b/backend/src/db/schemas/approval-policies.ts new file mode 100644 index 000000000..a69012e31 --- /dev/null +++ b/backend/src/db/schemas/approval-policies.ts @@ -0,0 +1,26 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalPoliciesSchema = z.object({ + id: z.string().uuid(), + projectId: z.string(), + organizationId: z.string().uuid(), + type: z.string(), + name: z.string(), + isActive: z.boolean().default(true).nullable().optional(), + maxRequestTtlSeconds: z.number().nullable().optional(), + conditions: z.unknown(), + constraints: z.unknown(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TApprovalPolicies = z.infer; +export type TApprovalPoliciesInsert = Omit, TImmutableDBKeys>; +export type TApprovalPoliciesUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/approval-policy-step-approvers.ts b/backend/src/db/schemas/approval-policy-step-approvers.ts new file mode 100644 index 000000000..909d99d15 --- /dev/null +++ b/backend/src/db/schemas/approval-policy-step-approvers.ts @@ -0,0 +1,24 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalPolicyStepApproversSchema = z.object({ + id: z.string().uuid(), + policyStepId: z.string().uuid(), + userId: z.string().uuid().nullable().optional(), + groupId: z.string().uuid().nullable().optional() +}); + +export type TApprovalPolicyStepApprovers = z.infer; +export type TApprovalPolicyStepApproversInsert = Omit< + z.input, + TImmutableDBKeys +>; +export type TApprovalPolicyStepApproversUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/db/schemas/approval-policy-steps.ts b/backend/src/db/schemas/approval-policy-steps.ts new file mode 100644 index 000000000..d4831fa2d --- /dev/null +++ b/backend/src/db/schemas/approval-policy-steps.ts @@ -0,0 +1,21 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalPolicyStepsSchema = z.object({ + id: z.string().uuid(), + policyId: z.string().uuid(), + name: z.string().nullable().optional(), + stepNumber: z.number(), + requiredApprovals: z.number(), + notifyApprovers: z.boolean().default(false).nullable().optional() +}); + +export type TApprovalPolicySteps = z.infer; +export type TApprovalPolicyStepsInsert = Omit, TImmutableDBKeys>; +export type TApprovalPolicyStepsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/approval-request-approvals.ts b/backend/src/db/schemas/approval-request-approvals.ts new file mode 100644 index 000000000..25b305d85 --- /dev/null +++ b/backend/src/db/schemas/approval-request-approvals.ts @@ -0,0 +1,23 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalRequestApprovalsSchema = z.object({ + id: z.string().uuid(), + stepId: z.string().uuid(), + approverUserId: z.string().uuid(), + decision: z.string(), + comment: z.string().nullable().optional(), + createdAt: z.date().nullable().optional() +}); + +export type TApprovalRequestApprovals = z.infer; +export type TApprovalRequestApprovalsInsert = Omit, TImmutableDBKeys>; +export type TApprovalRequestApprovalsUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/db/schemas/approval-request-grants.ts b/backend/src/db/schemas/approval-request-grants.ts new file mode 100644 index 000000000..7bf7703a6 --- /dev/null +++ b/backend/src/db/schemas/approval-request-grants.ts @@ -0,0 +1,27 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalRequestGrantsSchema = z.object({ + id: z.string().uuid(), + projectId: z.string(), + requestId: z.string().uuid(), + granteeUserId: z.string().uuid(), + revokedByUserId: z.string().uuid().nullable().optional(), + revocationReason: z.string().nullable().optional(), + status: z.string(), + type: z.string(), + attributes: z.unknown(), + createdAt: z.date().nullable().optional(), + expiresAt: z.date().nullable().optional(), + revokedAt: z.date().nullable().optional() +}); + +export type TApprovalRequestGrants = z.infer; +export type TApprovalRequestGrantsInsert = Omit, TImmutableDBKeys>; +export type TApprovalRequestGrantsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/approval-request-step-eligible-approvers.ts b/backend/src/db/schemas/approval-request-step-eligible-approvers.ts new file mode 100644 index 000000000..987861e6b --- /dev/null +++ b/backend/src/db/schemas/approval-request-step-eligible-approvers.ts @@ -0,0 +1,24 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalRequestStepEligibleApproversSchema = z.object({ + id: z.string().uuid(), + stepId: z.string().uuid(), + userId: z.string().uuid().nullable().optional(), + groupId: z.string().uuid().nullable().optional() +}); + +export type TApprovalRequestStepEligibleApprovers = z.infer; +export type TApprovalRequestStepEligibleApproversInsert = Omit< + z.input, + TImmutableDBKeys +>; +export type TApprovalRequestStepEligibleApproversUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/db/schemas/approval-request-steps.ts b/backend/src/db/schemas/approval-request-steps.ts new file mode 100644 index 000000000..b7b0d188d --- /dev/null +++ b/backend/src/db/schemas/approval-request-steps.ts @@ -0,0 +1,23 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalRequestStepsSchema = z.object({ + id: z.string().uuid(), + requestId: z.string().uuid(), + stepNumber: z.number(), + name: z.string().nullable().optional(), + status: z.string(), + requiredApprovals: z.number(), + startedAt: z.date().nullable().optional(), + completedAt: z.date().nullable().optional() +}); + +export type TApprovalRequestSteps = z.infer; +export type TApprovalRequestStepsInsert = Omit, TImmutableDBKeys>; +export type TApprovalRequestStepsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/approval-requests.ts b/backend/src/db/schemas/approval-requests.ts new file mode 100644 index 000000000..13b4a1ed8 --- /dev/null +++ b/backend/src/db/schemas/approval-requests.ts @@ -0,0 +1,30 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ApprovalRequestsSchema = z.object({ + id: z.string().uuid(), + projectId: z.string(), + organizationId: z.string().uuid(), + policyId: z.string().uuid(), + requesterId: z.string().uuid(), + requesterName: z.string(), + requesterEmail: z.string(), + type: z.string(), + status: z.string(), + justification: z.string().nullable().optional(), + currentStep: z.number(), + requestData: z.unknown(), + expiresAt: z.date().nullable().optional(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TApprovalRequests = z.infer; +export type TApprovalRequestsInsert = Omit, TImmutableDBKeys>; +export type TApprovalRequestsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index 7db6e847d..860020949 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -195,3 +195,11 @@ export * from "./users"; export * from "./vault-external-migration-configs"; export * from "./webhooks"; export * from "./workflow-integrations"; +export * from "./approval-policies"; +export * from "./approval-policy-step-approvers"; +export * from "./approval-policy-steps"; +export * from "./approval-request-approvals"; +export * from "./approval-request-grants"; +export * from "./approval-request-step-eligible-approvers"; +export * from "./approval-request-steps"; +export * from "./approval-requests"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 040d6e278..fe38a9c9b 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -223,7 +223,17 @@ export enum TableName { PkiAcmeOrder = "pki_acme_orders", PkiAcmeOrderAuth = "pki_acme_order_auths", PkiAcmeAuth = "pki_acme_auths", - PkiAcmeChallenge = "pki_acme_challenges" + PkiAcmeChallenge = "pki_acme_challenges", + + // Approval Policies + ApprovalPolicies = "approval_policies", + ApprovalPolicySteps = "approval_policy_steps", + ApprovalPolicyStepApprovers = "approval_policy_step_approvers", + ApprovalRequests = "approval_requests", + ApprovalRequestSteps = "approval_request_steps", + ApprovalRequestStepEligibleApprovers = "approval_request_step_eligible_approvers", + ApprovalRequestApprovals = "approval_request_approvals", + ApprovalRequestGrants = "approval_request_grants" } export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";