chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
type ContractJsonResponse,
|
||||
type ContractQuery,
|
||||
type ContractQueryInput,
|
||||
defineRouteContract,
|
||||
} from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1PaginationMetaSchema,
|
||||
adminV1QueryStringSchema,
|
||||
adminV1SingleResponseSchema,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
|
||||
export const adminV1PermissionGroupSchema = z.object({
|
||||
id: z.string(),
|
||||
organizationId: z.string(),
|
||||
organizationName: z.string().nullable(),
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
isDefault: z.boolean(),
|
||||
memberCount: z.number(),
|
||||
createdAt: z.string(),
|
||||
createdByUserId: z.string(),
|
||||
createdByEmail: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const adminV1AccessControlQuerySchema = z.object({
|
||||
organizationId: adminV1QueryStringSchema,
|
||||
})
|
||||
|
||||
export const adminV1AccessControlDeleteQuerySchema = adminV1AccessControlQuerySchema
|
||||
.extend({
|
||||
reason: adminV1QueryStringSchema.default('Enterprise plan churn cleanup'),
|
||||
})
|
||||
.refine((query) => Boolean(query.organizationId), {
|
||||
error: 'organizationId is required',
|
||||
})
|
||||
|
||||
const adminV1AccessControlListResultSchema = z.object({
|
||||
data: z.array(adminV1PermissionGroupSchema),
|
||||
pagination: adminV1PaginationMetaSchema,
|
||||
})
|
||||
|
||||
const adminV1AccessControlDeleteResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
deletedCount: z.number(),
|
||||
membersRemoved: z.number(),
|
||||
reason: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
})
|
||||
|
||||
export const adminV1ListAccessControlContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/access-control',
|
||||
query: adminV1AccessControlQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1AccessControlListResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1DeleteAccessControlContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/access-control',
|
||||
query: adminV1AccessControlDeleteQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1AccessControlDeleteResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1PermissionGroup = z.output<typeof adminV1PermissionGroupSchema>
|
||||
export type AdminV1ListAccessControlQueryInput = ContractQueryInput<
|
||||
typeof adminV1ListAccessControlContract
|
||||
>
|
||||
export type AdminV1ListAccessControlQuery = ContractQuery<typeof adminV1ListAccessControlContract>
|
||||
export type AdminV1DeleteAccessControlQueryInput = ContractQueryInput<
|
||||
typeof adminV1DeleteAccessControlContract
|
||||
>
|
||||
export type AdminV1DeleteAccessControlQuery = ContractQuery<
|
||||
typeof adminV1DeleteAccessControlContract
|
||||
>
|
||||
export type AdminV1ListAccessControlResponse = ContractJsonResponse<
|
||||
typeof adminV1ListAccessControlContract
|
||||
>
|
||||
export type AdminV1DeleteAccessControlResponse = ContractJsonResponse<
|
||||
typeof adminV1DeleteAccessControlContract
|
||||
>
|
||||
@@ -0,0 +1,111 @@
|
||||
import { z } from 'zod'
|
||||
import { type ContractJsonResponse, defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1IdParamsSchema,
|
||||
adminV1ListResponseSchema,
|
||||
adminV1PaginationQuerySchema,
|
||||
adminV1QueryStringSchema,
|
||||
adminV1SingleResponseSchema,
|
||||
adminV1SubscriptionSchema,
|
||||
lastQueryValue,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
|
||||
export const adminV1ListSubscriptionsQuerySchema = adminV1PaginationQuerySchema.extend({
|
||||
plan: adminV1QueryStringSchema,
|
||||
status: adminV1QueryStringSchema,
|
||||
})
|
||||
|
||||
export const adminV1CancelSubscriptionQuerySchema = z.object({
|
||||
atPeriodEnd: z
|
||||
.preprocess(lastQueryValue, z.unknown())
|
||||
.pipe(z.enum(['true', 'false']).catch('false'))
|
||||
.transform((value) => value === 'true'),
|
||||
reason: adminV1QueryStringSchema.default('Admin cancellation (no reason provided)'),
|
||||
})
|
||||
|
||||
const adminV1CancelSubscriptionResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
message: z.string(),
|
||||
subscriptionId: z.string(),
|
||||
stripeSubscriptionId: z.string(),
|
||||
atPeriodEnd: z.boolean(),
|
||||
periodEnd: z.string().nullable().optional(),
|
||||
})
|
||||
|
||||
export const adminV1ListSubscriptionsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/subscriptions',
|
||||
query: adminV1ListSubscriptionsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1SubscriptionSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetSubscriptionContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/subscriptions/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1SubscriptionSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1CancelSubscriptionContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/subscriptions/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1CancelSubscriptionQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1CancelSubscriptionResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1IssueCreditsBodySchema = z
|
||||
.object({
|
||||
userId: z.string({ error: 'userId must be a string' }).optional(),
|
||||
email: z.string({ error: 'email must be a string' }).optional(),
|
||||
amount: z
|
||||
.number({ error: 'amount must be a positive number' })
|
||||
.finite({ error: 'amount must be a positive number' })
|
||||
.positive({ error: 'amount must be a positive number' }),
|
||||
reason: z.string().optional(),
|
||||
})
|
||||
.refine((body) => body.userId || body.email, {
|
||||
error: 'Either userId or email is required',
|
||||
})
|
||||
export type AdminV1IssueCreditsBodyInput = z.input<typeof adminV1IssueCreditsBodySchema>
|
||||
export type AdminV1IssueCreditsBody = z.output<typeof adminV1IssueCreditsBodySchema>
|
||||
|
||||
const adminV1IssueCreditsResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
userId: z.string(),
|
||||
userEmail: z.string().nullable(),
|
||||
entityType: z.enum(['user', 'organization']),
|
||||
entityId: z.string(),
|
||||
amount: z.number(),
|
||||
newCreditBalance: z.number(),
|
||||
newUsageLimit: z.number(),
|
||||
})
|
||||
|
||||
export const adminV1IssueCreditsContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/credits',
|
||||
body: adminV1IssueCreditsBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1IssueCreditsResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1ListSubscriptionsResponse = ContractJsonResponse<
|
||||
typeof adminV1ListSubscriptionsContract
|
||||
>
|
||||
export type AdminV1GetSubscriptionResponse = ContractJsonResponse<
|
||||
typeof adminV1GetSubscriptionContract
|
||||
>
|
||||
export type AdminV1CancelSubscriptionResponse = ContractJsonResponse<
|
||||
typeof adminV1CancelSubscriptionContract
|
||||
>
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from '@/lib/api/contracts/v1/admin/access-control'
|
||||
export * from '@/lib/api/contracts/v1/admin/billing'
|
||||
export * from '@/lib/api/contracts/v1/admin/organizations'
|
||||
export * from '@/lib/api/contracts/v1/admin/outbox'
|
||||
export * from '@/lib/api/contracts/v1/admin/referral-campaigns'
|
||||
export * from '@/lib/api/contracts/v1/admin/shared'
|
||||
export * from '@/lib/api/contracts/v1/admin/users'
|
||||
export * from '@/lib/api/contracts/v1/admin/workflows'
|
||||
export * from '@/lib/api/contracts/v1/admin/workspaces'
|
||||
@@ -0,0 +1,342 @@
|
||||
import { z } from 'zod'
|
||||
import { type ContractJsonResponse, defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1BooleanQuerySchema,
|
||||
adminV1IdParamsSchema,
|
||||
adminV1ListResponseSchema,
|
||||
adminV1PaginationQuerySchema,
|
||||
adminV1SingleResponseSchema,
|
||||
adminV1SubscriptionSchema,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
|
||||
export const adminV1OrganizationMemberParamsSchema = adminV1IdParamsSchema.extend({
|
||||
memberId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const adminV1OrganizationSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
logo: z.string().nullable(),
|
||||
orgUsageLimit: z.string().nullable(),
|
||||
storageUsedBytes: z.number(),
|
||||
departedMemberUsage: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1OrganizationDetailSchema = adminV1OrganizationSchema.extend({
|
||||
memberCount: z.number(),
|
||||
subscription: adminV1SubscriptionSchema.nullable(),
|
||||
})
|
||||
|
||||
export const adminV1MemberSchema = z.object({
|
||||
id: z.string(),
|
||||
userId: z.string(),
|
||||
organizationId: z.string(),
|
||||
role: z.string(),
|
||||
createdAt: z.string(),
|
||||
userName: z.string(),
|
||||
userEmail: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1MemberDetailSchema = adminV1MemberSchema.extend({
|
||||
currentPeriodCost: z.string(),
|
||||
currentUsageLimit: z.string().nullable(),
|
||||
billingBlocked: z.boolean(),
|
||||
})
|
||||
|
||||
export const adminV1OrganizationBillingSummarySchema = z.object({
|
||||
organizationId: z.string(),
|
||||
organizationName: z.string(),
|
||||
subscriptionPlan: z.string(),
|
||||
subscriptionStatus: z.string(),
|
||||
totalSeats: z.number(),
|
||||
usedSeats: z.number(),
|
||||
availableSeats: z.number(),
|
||||
totalCurrentUsage: z.number(),
|
||||
totalUsageLimit: z.number(),
|
||||
minimumBillingAmount: z.number(),
|
||||
averageUsagePerMember: z.number(),
|
||||
usagePercentage: z.number(),
|
||||
billingPeriodStart: z.string().nullable(),
|
||||
billingPeriodEnd: z.string().nullable(),
|
||||
membersOverLimit: z.number(),
|
||||
membersNearLimit: z.number(),
|
||||
})
|
||||
|
||||
export const adminV1SeatAnalyticsSchema = z.object({
|
||||
organizationId: z.string(),
|
||||
organizationName: z.string(),
|
||||
currentSeats: z.number(),
|
||||
maxSeats: z.number(),
|
||||
availableSeats: z.number(),
|
||||
subscriptionPlan: z.string(),
|
||||
canAddSeats: z.boolean(),
|
||||
utilizationRate: z.number(),
|
||||
})
|
||||
|
||||
export const adminV1CreateOrganizationBodySchema = z.object({
|
||||
name: z.string({ error: 'name is required' }).trim().min(1, { error: 'name is required' }),
|
||||
ownerId: z.string({ error: 'ownerId is required' }).min(1, { error: 'ownerId is required' }),
|
||||
slug: z.string().optional(),
|
||||
})
|
||||
|
||||
export const adminV1UpdateOrganizationBodySchema = z.object({
|
||||
name: z
|
||||
.string({ error: 'name must be a non-empty string' })
|
||||
.trim()
|
||||
.min(1, { error: 'name must be a non-empty string' })
|
||||
.optional(),
|
||||
slug: z
|
||||
.string({ error: 'slug must be a non-empty string' })
|
||||
.trim()
|
||||
.min(1, { error: 'slug must be a non-empty string' })
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export const adminV1AddOrganizationMemberBodySchema = z.object({
|
||||
userId: z.string({ error: 'userId is required' }).min(1, { error: 'userId is required' }),
|
||||
role: z.enum(['admin', 'member'], { error: 'role must be "admin" or "member"' }),
|
||||
})
|
||||
|
||||
export const adminV1UpdateOrganizationMemberBodySchema = z.object({
|
||||
role: z.enum(['admin', 'member'], { error: 'role must be "admin" or "member"' }),
|
||||
})
|
||||
|
||||
export const adminV1RemoveOrganizationMemberQuerySchema = z.object({
|
||||
skipBillingLogic: adminV1BooleanQuerySchema,
|
||||
})
|
||||
|
||||
export const adminV1UpdateOrganizationBillingBodySchema = z.object({
|
||||
orgUsageLimit: z
|
||||
.union([
|
||||
z
|
||||
.number({ error: 'orgUsageLimit must be a non-negative number or null' })
|
||||
.min(0, { error: 'orgUsageLimit must be a non-negative number or null' }),
|
||||
z.null(),
|
||||
])
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export const adminV1TransferOwnershipBodySchema = z.object({
|
||||
newOwnerUserId: z
|
||||
.string({ error: 'newOwnerUserId is required' })
|
||||
.min(1, { error: 'newOwnerUserId is required' }),
|
||||
currentOwnerUserId: z
|
||||
.string({ error: 'currentOwnerUserId must be a non-empty string when provided' })
|
||||
.min(1, { error: 'currentOwnerUserId must be a non-empty string when provided' })
|
||||
.optional(),
|
||||
})
|
||||
|
||||
const adminV1OrganizationMemberMutationResultSchema = adminV1MemberSchema.extend({
|
||||
action: z.enum(['created', 'updated', 'already_member']),
|
||||
billingActions: z.object({
|
||||
proUsageSnapshotted: z.boolean(),
|
||||
proCancelledAtPeriodEnd: z.boolean(),
|
||||
}),
|
||||
})
|
||||
|
||||
const adminV1RemoveOrganizationMemberResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
memberId: z.string(),
|
||||
userId: z.string(),
|
||||
billingActions: z.object({
|
||||
usageCaptured: z.boolean(),
|
||||
proRestored: z.boolean(),
|
||||
usageRestored: z.boolean(),
|
||||
skipBillingLogic: z.boolean(),
|
||||
}),
|
||||
})
|
||||
|
||||
const adminV1OrganizationBillingUpdateResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
orgUsageLimit: z.string().nullable(),
|
||||
})
|
||||
|
||||
const adminV1TransferOwnershipResultSchema = z.object({
|
||||
organizationId: z.string(),
|
||||
currentOwnerUserId: z.string(),
|
||||
newOwnerUserId: z.string(),
|
||||
workspacesReassigned: z.number(),
|
||||
billedAccountReassigned: z.boolean(),
|
||||
overageMigrated: z.boolean(),
|
||||
billingBlockInherited: z.boolean(),
|
||||
})
|
||||
|
||||
export const adminV1ListOrganizationsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/organizations',
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1OrganizationSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1CreateOrganizationContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/organizations',
|
||||
body: adminV1CreateOrganizationBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1OrganizationSchema.extend({ memberId: z.string() })),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetOrganizationContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/organizations/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1OrganizationDetailSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1UpdateOrganizationContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/admin/organizations/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
body: adminV1UpdateOrganizationBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1OrganizationSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ListOrganizationMembersContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/organizations/[id]/members',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1MemberDetailSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1AddOrganizationMemberContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/organizations/[id]/members',
|
||||
params: adminV1IdParamsSchema,
|
||||
body: adminV1AddOrganizationMemberBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1OrganizationMemberMutationResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetOrganizationMemberContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/organizations/[id]/members/[memberId]',
|
||||
params: adminV1OrganizationMemberParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1MemberDetailSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1UpdateOrganizationMemberContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/admin/organizations/[id]/members/[memberId]',
|
||||
params: adminV1OrganizationMemberParamsSchema,
|
||||
body: adminV1UpdateOrganizationMemberBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1MemberSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1RemoveOrganizationMemberContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/organizations/[id]/members/[memberId]',
|
||||
params: adminV1OrganizationMemberParamsSchema,
|
||||
query: adminV1RemoveOrganizationMemberQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1RemoveOrganizationMemberResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetOrganizationBillingContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/organizations/[id]/billing',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1OrganizationBillingSummarySchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1UpdateOrganizationBillingContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/admin/organizations/[id]/billing',
|
||||
params: adminV1IdParamsSchema,
|
||||
body: adminV1UpdateOrganizationBillingBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1OrganizationBillingUpdateResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetOrganizationSeatsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/organizations/[id]/seats',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1SeatAnalyticsSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1TransferOwnershipContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/organizations/[id]/transfer-ownership',
|
||||
params: adminV1IdParamsSchema,
|
||||
body: adminV1TransferOwnershipBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1TransferOwnershipResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1ListOrganizationsResponse = ContractJsonResponse<
|
||||
typeof adminV1ListOrganizationsContract
|
||||
>
|
||||
export type AdminV1CreateOrganizationResponse = ContractJsonResponse<
|
||||
typeof adminV1CreateOrganizationContract
|
||||
>
|
||||
export type AdminV1GetOrganizationResponse = ContractJsonResponse<
|
||||
typeof adminV1GetOrganizationContract
|
||||
>
|
||||
export type AdminV1UpdateOrganizationResponse = ContractJsonResponse<
|
||||
typeof adminV1UpdateOrganizationContract
|
||||
>
|
||||
export type AdminV1ListOrganizationMembersResponse = ContractJsonResponse<
|
||||
typeof adminV1ListOrganizationMembersContract
|
||||
>
|
||||
export type AdminV1AddOrganizationMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1AddOrganizationMemberContract
|
||||
>
|
||||
export type AdminV1GetOrganizationMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1GetOrganizationMemberContract
|
||||
>
|
||||
export type AdminV1UpdateOrganizationMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1UpdateOrganizationMemberContract
|
||||
>
|
||||
export type AdminV1RemoveOrganizationMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1RemoveOrganizationMemberContract
|
||||
>
|
||||
export type AdminV1GetOrganizationBillingResponse = ContractJsonResponse<
|
||||
typeof adminV1GetOrganizationBillingContract
|
||||
>
|
||||
export type AdminV1UpdateOrganizationBillingResponse = ContractJsonResponse<
|
||||
typeof adminV1UpdateOrganizationBillingContract
|
||||
>
|
||||
export type AdminV1GetOrganizationSeatsResponse = ContractJsonResponse<
|
||||
typeof adminV1GetOrganizationSeatsContract
|
||||
>
|
||||
export type AdminV1TransferOwnershipResponse = ContractJsonResponse<
|
||||
typeof adminV1TransferOwnershipContract
|
||||
>
|
||||
@@ -0,0 +1,93 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
type ContractJsonResponse,
|
||||
type ContractParams,
|
||||
type ContractQuery,
|
||||
type ContractQueryInput,
|
||||
defineRouteContract,
|
||||
} from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1IdParamsSchema,
|
||||
adminV1QueryStringSchema,
|
||||
lastQueryValue,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
|
||||
const adminV1OutboxStatuses = ['pending', 'processing', 'completed', 'dead_letter'] as const
|
||||
|
||||
export const adminV1OutboxQuerySchema = z.object({
|
||||
status: z.preprocess(
|
||||
lastQueryValue,
|
||||
z
|
||||
.enum(adminV1OutboxStatuses, {
|
||||
error: `Invalid status. Must be one of: ${adminV1OutboxStatuses.join(', ')}`,
|
||||
})
|
||||
.optional()
|
||||
.default('dead_letter')
|
||||
),
|
||||
eventType: adminV1QueryStringSchema.transform((value) => value ?? null),
|
||||
limit: z
|
||||
.preprocess((value) => {
|
||||
const queryValue = lastQueryValue(value)
|
||||
return typeof queryValue === 'string' ? Number.parseInt(queryValue, 10) : queryValue
|
||||
}, z.number().int().catch(100))
|
||||
.catch(100)
|
||||
.transform((limit) => {
|
||||
if (!Number.isFinite(limit) || limit <= 0) return 100
|
||||
return Math.min(500, Math.max(1, limit))
|
||||
}),
|
||||
})
|
||||
|
||||
const adminV1OutboxListResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
filter: z.object({
|
||||
status: z.enum(adminV1OutboxStatuses),
|
||||
eventType: z.string().nullable(),
|
||||
limit: z.number(),
|
||||
}),
|
||||
rows: z.array(z.unknown()),
|
||||
counts: z.array(
|
||||
z.object({
|
||||
status: z.string(),
|
||||
eventType: z.string(),
|
||||
count: z.number(),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
const adminV1OutboxRequeueResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
requeued: z.object({
|
||||
id: z.string(),
|
||||
eventType: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
export const adminV1ListOutboxContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/outbox',
|
||||
query: adminV1OutboxQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1OutboxListResultSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1RequeueOutboxEventContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/outbox/[id]/requeue',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1OutboxRequeueResultSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1ListOutboxQueryInput = ContractQueryInput<typeof adminV1ListOutboxContract>
|
||||
export type AdminV1ListOutboxQuery = ContractQuery<typeof adminV1ListOutboxContract>
|
||||
export type AdminV1RequeueOutboxEventParams = ContractParams<
|
||||
typeof adminV1RequeueOutboxEventContract
|
||||
>
|
||||
export type AdminV1ListOutboxResponse = ContractJsonResponse<typeof adminV1ListOutboxContract>
|
||||
export type AdminV1RequeueOutboxEventResponse = ContractJsonResponse<
|
||||
typeof adminV1RequeueOutboxEventContract
|
||||
>
|
||||
@@ -0,0 +1,177 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
type ContractBody,
|
||||
type ContractBodyInput,
|
||||
type ContractJsonResponse,
|
||||
type ContractQuery,
|
||||
type ContractQueryInput,
|
||||
defineRouteContract,
|
||||
} from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1FutureIsoDateSchema,
|
||||
adminV1QueryStringSchema,
|
||||
adminV1SingleResponseSchema,
|
||||
lastQueryValue,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
|
||||
const adminV1ReferralCampaignDurations = ['once', 'repeating', 'forever'] as const
|
||||
const adminV1ReferralCampaignAppliesTo = [
|
||||
'pro',
|
||||
'team',
|
||||
'pro_6000',
|
||||
'pro_25000',
|
||||
'team_6000',
|
||||
'team_25000',
|
||||
] as const
|
||||
|
||||
export const adminV1PromoCodeSchema = z.object({
|
||||
id: z.string(),
|
||||
code: z.string(),
|
||||
couponId: z.string(),
|
||||
name: z.string(),
|
||||
percentOff: z.number(),
|
||||
duration: z.string(),
|
||||
durationInMonths: z.number().nullable(),
|
||||
appliesToProductIds: z.array(z.string()).nullable(),
|
||||
maxRedemptions: z.number().nullable(),
|
||||
expiresAt: z.string().nullable(),
|
||||
active: z.boolean(),
|
||||
timesRedeemed: z.number(),
|
||||
createdAt: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1ReferralCampaignQuerySchema = z.object({
|
||||
limit: z
|
||||
.preprocess((value) => {
|
||||
const queryValue = lastQueryValue(value)
|
||||
return typeof queryValue === 'string' ? Number.parseInt(queryValue, 10) : queryValue
|
||||
}, z.number().int().catch(50))
|
||||
.catch(50)
|
||||
.transform((limit) => {
|
||||
if (limit < 1) return 50
|
||||
return Math.min(limit, 100)
|
||||
}),
|
||||
starting_after: adminV1QueryStringSchema,
|
||||
active: z
|
||||
.preprocess(lastQueryValue, z.enum(['true', 'false']).optional().catch(undefined))
|
||||
.transform((active) => (active === undefined ? undefined : active === 'true')),
|
||||
})
|
||||
|
||||
export const adminV1ReferralCampaignBodySchema = z
|
||||
.object({
|
||||
name: z
|
||||
.string({ error: 'name is required and must be a non-empty string' })
|
||||
.trim()
|
||||
.min(1, { error: 'name is required and must be a non-empty string' }),
|
||||
percentOff: z
|
||||
.number({ error: 'percentOff must be a number between 1 and 100' })
|
||||
.finite({ error: 'percentOff must be a number between 1 and 100' })
|
||||
.min(1, { error: 'percentOff must be a number between 1 and 100' })
|
||||
.max(100, { error: 'percentOff must be a number between 1 and 100' }),
|
||||
code: z
|
||||
.union([
|
||||
z
|
||||
.string({ error: 'code must be a string or null' })
|
||||
.trim()
|
||||
.min(6, { error: 'code must be at least 6 characters' }),
|
||||
z.null(),
|
||||
])
|
||||
.optional(),
|
||||
duration: z
|
||||
.enum(adminV1ReferralCampaignDurations, {
|
||||
error: `duration must be one of: ${adminV1ReferralCampaignDurations.join(', ')}`,
|
||||
})
|
||||
.optional()
|
||||
.default('once'),
|
||||
durationInMonths: z
|
||||
.number({
|
||||
error:
|
||||
'durationInMonths is required and must be a positive integer when duration is "repeating"',
|
||||
})
|
||||
.int({
|
||||
error:
|
||||
'durationInMonths is required and must be a positive integer when duration is "repeating"',
|
||||
})
|
||||
.min(1, {
|
||||
error:
|
||||
'durationInMonths is required and must be a positive integer when duration is "repeating"',
|
||||
})
|
||||
.optional(),
|
||||
maxRedemptions: z
|
||||
.union([
|
||||
z
|
||||
.number({ error: 'maxRedemptions must be a positive integer' })
|
||||
.int({ error: 'maxRedemptions must be a positive integer' })
|
||||
.min(1, { error: 'maxRedemptions must be a positive integer' }),
|
||||
z.null(),
|
||||
])
|
||||
.optional(),
|
||||
expiresAt: z.union([adminV1FutureIsoDateSchema, z.null()]).optional(),
|
||||
appliesTo: z
|
||||
.union([
|
||||
z
|
||||
.array(z.enum(adminV1ReferralCampaignAppliesTo), {
|
||||
error: 'appliesTo must be a non-empty array',
|
||||
})
|
||||
.nonempty({ error: 'appliesTo must be a non-empty array' }),
|
||||
z.null(),
|
||||
])
|
||||
.optional(),
|
||||
})
|
||||
.refine((body) => body.duration !== 'repeating' || body.durationInMonths !== undefined, {
|
||||
error:
|
||||
'durationInMonths is required and must be a positive integer when duration is "repeating"',
|
||||
path: ['durationInMonths'],
|
||||
})
|
||||
|
||||
const adminV1ReferralCampaignListResultSchema = z.object({
|
||||
data: z.array(adminV1PromoCodeSchema),
|
||||
hasMore: z.boolean(),
|
||||
nextCursor: z.string().optional(),
|
||||
})
|
||||
|
||||
export const adminV1ListReferralCampaignsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/referral-campaigns',
|
||||
query: adminV1ReferralCampaignQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ReferralCampaignListResultSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1CreateReferralCampaignContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/referral-campaigns',
|
||||
body: adminV1ReferralCampaignBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1PromoCodeSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1PromoCode = z.output<typeof adminV1PromoCodeSchema>
|
||||
export type AdminV1ReferralCampaignDuration = ContractBody<
|
||||
typeof adminV1CreateReferralCampaignContract
|
||||
>['duration']
|
||||
export type AdminV1ReferralCampaignAppliesTo = NonNullable<
|
||||
ContractBody<typeof adminV1CreateReferralCampaignContract>['appliesTo']
|
||||
>[number]
|
||||
export type AdminV1ListReferralCampaignsQueryInput = ContractQueryInput<
|
||||
typeof adminV1ListReferralCampaignsContract
|
||||
>
|
||||
export type AdminV1ListReferralCampaignsQuery = ContractQuery<
|
||||
typeof adminV1ListReferralCampaignsContract
|
||||
>
|
||||
export type AdminV1CreateReferralCampaignBodyInput = ContractBodyInput<
|
||||
typeof adminV1CreateReferralCampaignContract
|
||||
>
|
||||
export type AdminV1CreateReferralCampaignBody = ContractBody<
|
||||
typeof adminV1CreateReferralCampaignContract
|
||||
>
|
||||
export type AdminV1ListReferralCampaignsResponse = ContractJsonResponse<
|
||||
typeof adminV1ListReferralCampaignsContract
|
||||
>
|
||||
export type AdminV1CreateReferralCampaignResponse = ContractJsonResponse<
|
||||
typeof adminV1CreateReferralCampaignContract
|
||||
>
|
||||
@@ -0,0 +1,93 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const adminV1DefaultLimit = 50
|
||||
export const adminV1MaxLimit = 250
|
||||
|
||||
export const lastQueryValue = (value: unknown) => (Array.isArray(value) ? value.at(-1) : value)
|
||||
|
||||
export const adminV1IdParamsSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
})
|
||||
|
||||
export const adminV1PaginationQuerySchema = z.object({
|
||||
limit: z
|
||||
.preprocess((value) => {
|
||||
const queryValue = lastQueryValue(value)
|
||||
return typeof queryValue === 'string' ? Number.parseInt(queryValue, 10) : queryValue
|
||||
}, z.number().int().catch(adminV1DefaultLimit))
|
||||
.catch(adminV1DefaultLimit)
|
||||
.transform((limit) => {
|
||||
if (limit < 1) return adminV1DefaultLimit
|
||||
return Math.min(limit, adminV1MaxLimit)
|
||||
}),
|
||||
offset: z
|
||||
.preprocess((value) => {
|
||||
const queryValue = lastQueryValue(value)
|
||||
return typeof queryValue === 'string' ? Number.parseInt(queryValue, 10) : queryValue
|
||||
}, z.number().int().catch(0))
|
||||
.catch(0)
|
||||
.transform((offset) => {
|
||||
if (offset < 0) return 0
|
||||
return offset
|
||||
}),
|
||||
})
|
||||
|
||||
export const adminV1BooleanQuerySchema = z
|
||||
.preprocess(lastQueryValue, z.enum(['true', 'false']).optional().catch(undefined))
|
||||
.transform((value) => value === 'true')
|
||||
|
||||
export const adminV1ExportFormatQuerySchema = z.object({
|
||||
format: z.preprocess(lastQueryValue, z.enum(['zip', 'json']).catch('zip')),
|
||||
})
|
||||
|
||||
export const adminV1QueryStringSchema = z.preprocess(lastQueryValue, z.string().optional())
|
||||
|
||||
export const adminV1FutureIsoDateSchema = z
|
||||
.string({ error: 'expiresAt must be a valid ISO 8601 date string' })
|
||||
.refine((value) => !Number.isNaN(Date.parse(value)), {
|
||||
error: 'expiresAt must be a valid ISO 8601 date string',
|
||||
})
|
||||
.refine((value) => new Date(value).getTime() > Date.now(), {
|
||||
error: 'expiresAt must be in the future',
|
||||
})
|
||||
|
||||
export const adminV1PaginationMetaSchema = z.object({
|
||||
total: z.number(),
|
||||
limit: z.number(),
|
||||
offset: z.number(),
|
||||
hasMore: z.boolean(),
|
||||
})
|
||||
|
||||
export const adminV1ListResponseSchema = <T extends z.ZodType>(itemSchema: T) =>
|
||||
z.object({
|
||||
data: z.array(itemSchema),
|
||||
pagination: adminV1PaginationMetaSchema,
|
||||
})
|
||||
|
||||
export const adminV1SingleResponseSchema = <T extends z.ZodType>(dataSchema: T) =>
|
||||
z.object({
|
||||
data: dataSchema,
|
||||
})
|
||||
|
||||
export const adminV1SubscriptionSchema = z.object({
|
||||
id: z.string(),
|
||||
plan: z.string(),
|
||||
referenceId: z.string(),
|
||||
stripeCustomerId: z.string().nullable(),
|
||||
stripeSubscriptionId: z.string().nullable(),
|
||||
status: z.string().nullable(),
|
||||
periodStart: z.string().nullable(),
|
||||
periodEnd: z.string().nullable(),
|
||||
cancelAtPeriodEnd: z.boolean().nullable(),
|
||||
seats: z.number().nullable(),
|
||||
trialStart: z.string().nullable(),
|
||||
trialEnd: z.string().nullable(),
|
||||
metadata: z.unknown(),
|
||||
})
|
||||
|
||||
export type AdminV1IdParamsInput = z.input<typeof adminV1IdParamsSchema>
|
||||
export type AdminV1IdParams = z.output<typeof adminV1IdParamsSchema>
|
||||
export type AdminV1PaginationQueryInput = z.input<typeof adminV1PaginationQuerySchema>
|
||||
export type AdminV1PaginationQuery = z.output<typeof adminV1PaginationQuerySchema>
|
||||
export type AdminV1ExportFormatQueryInput = z.input<typeof adminV1ExportFormatQuerySchema>
|
||||
export type AdminV1ExportFormatQuery = z.output<typeof adminV1ExportFormatQuerySchema>
|
||||
@@ -0,0 +1,119 @@
|
||||
import { z } from 'zod'
|
||||
import { type ContractJsonResponse, defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1IdParamsSchema,
|
||||
adminV1ListResponseSchema,
|
||||
adminV1PaginationQuerySchema,
|
||||
adminV1SingleResponseSchema,
|
||||
adminV1SubscriptionSchema,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
|
||||
export const adminV1UserSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
emailVerified: z.boolean(),
|
||||
image: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1UserBillingSchema = z.object({
|
||||
userId: z.string(),
|
||||
userName: z.string(),
|
||||
userEmail: z.string(),
|
||||
stripeCustomerId: z.string().nullable(),
|
||||
currentUsageLimit: z.string().nullable(),
|
||||
currentPeriodCost: z.string(),
|
||||
lastPeriodCost: z.string().nullable(),
|
||||
billedOverageThisPeriod: z.string(),
|
||||
storageUsedBytes: z.number(),
|
||||
billingBlocked: z.boolean(),
|
||||
currentPeriodCopilotCost: z.string(),
|
||||
lastPeriodCopilotCost: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const adminV1UserBillingWithSubscriptionSchema = adminV1UserBillingSchema.extend({
|
||||
subscriptions: z.array(adminV1SubscriptionSchema),
|
||||
organizationMemberships: z.array(
|
||||
z.object({
|
||||
organizationId: z.string(),
|
||||
organizationName: z.string(),
|
||||
role: z.string(),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const adminV1UpdateUserBillingBodySchema = z.object({
|
||||
currentUsageLimit: z
|
||||
.union([
|
||||
z
|
||||
.number({ error: 'currentUsageLimit must be a non-negative number or null' })
|
||||
.min(0, { error: 'currentUsageLimit must be a non-negative number or null' }),
|
||||
z.null(),
|
||||
])
|
||||
.optional(),
|
||||
billingBlocked: z.boolean({ error: 'billingBlocked must be a boolean' }).optional(),
|
||||
currentPeriodCost: z
|
||||
.number({ error: 'currentPeriodCost must be a non-negative number' })
|
||||
.min(0, { error: 'currentPeriodCost must be a non-negative number' })
|
||||
.optional(),
|
||||
reason: z.string().optional(),
|
||||
})
|
||||
|
||||
const adminV1UserBillingUpdateResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
updated: z.array(z.string()),
|
||||
warnings: z.array(z.string()),
|
||||
reason: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1ListUsersContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/users',
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1UserSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetUserContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/users/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1UserSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetUserBillingContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/users/[id]/billing',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1UserBillingWithSubscriptionSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1UpdateUserBillingContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/admin/users/[id]/billing',
|
||||
params: adminV1IdParamsSchema,
|
||||
body: adminV1UpdateUserBillingBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1UserBillingUpdateResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1ListUsersResponse = ContractJsonResponse<typeof adminV1ListUsersContract>
|
||||
export type AdminV1GetUserResponse = ContractJsonResponse<typeof adminV1GetUserContract>
|
||||
export type AdminV1GetUserBillingResponse = ContractJsonResponse<
|
||||
typeof adminV1GetUserBillingContract
|
||||
>
|
||||
export type AdminV1UpdateUserBillingResponse = ContractJsonResponse<
|
||||
typeof adminV1UpdateUserBillingContract
|
||||
>
|
||||
@@ -0,0 +1,319 @@
|
||||
import { z } from 'zod'
|
||||
import { type ContractJsonResponse, defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1ExportFormatQuerySchema,
|
||||
adminV1IdParamsSchema,
|
||||
adminV1ListResponseSchema,
|
||||
adminV1PaginationQuerySchema,
|
||||
adminV1SingleResponseSchema,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
import { workflowStateSchema } from '@/lib/api/contracts/workflows'
|
||||
|
||||
export const adminV1WorkflowVersionParamsSchema = adminV1IdParamsSchema.extend({
|
||||
versionId: z
|
||||
.string()
|
||||
.transform((value) => Number(value))
|
||||
.refine((value) => Number.isFinite(value) && value >= 1, {
|
||||
error: 'Invalid version number',
|
||||
}),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
workspaceId: z.string().nullable(),
|
||||
folderId: z.string().nullable(),
|
||||
isDeployed: z.boolean(),
|
||||
deployedAt: z.string().nullable(),
|
||||
runCount: z.number(),
|
||||
lastRunAt: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowDetailSchema = adminV1WorkflowSchema.extend({
|
||||
blockCount: z.number(),
|
||||
edgeCount: z.number(),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowVariableSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
type: z.enum(['string', 'number', 'boolean', 'object', 'array', 'plain']),
|
||||
value: z.unknown(),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowExportStateSchema = workflowStateSchema.extend({
|
||||
metadata: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
exportedAt: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
variables: z.record(z.string(), adminV1WorkflowVariableSchema).optional(),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowExportPayloadSchema = z.object({
|
||||
version: z.literal('1.0'),
|
||||
exportedAt: z.string(),
|
||||
workflow: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
workspaceId: z.string().nullable(),
|
||||
folderId: z.string().nullable(),
|
||||
}),
|
||||
state: adminV1WorkflowExportStateSchema,
|
||||
})
|
||||
|
||||
export const adminV1FolderExportPayloadSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
parentId: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceExportPayloadSchema = z.object({
|
||||
version: z.literal('1.0'),
|
||||
exportedAt: z.string(),
|
||||
workspace: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
workflows: z.array(
|
||||
z.object({
|
||||
workflow: adminV1WorkflowExportPayloadSchema.shape.workflow,
|
||||
state: adminV1WorkflowExportStateSchema,
|
||||
})
|
||||
),
|
||||
folders: z.array(adminV1FolderExportPayloadSchema),
|
||||
})
|
||||
|
||||
export const adminV1FolderFullExportPayloadSchema = z.object({
|
||||
version: z.literal('1.0'),
|
||||
exportedAt: z.string(),
|
||||
folder: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
workflows: z.array(
|
||||
z.object({
|
||||
workflow: adminV1WorkflowExportPayloadSchema.shape.workflow.omit({ workspaceId: true }),
|
||||
state: adminV1WorkflowExportStateSchema,
|
||||
})
|
||||
),
|
||||
folders: z.array(adminV1FolderExportPayloadSchema),
|
||||
})
|
||||
|
||||
export const adminV1ImportResultSchema = z.object({
|
||||
workflowId: z.string(),
|
||||
name: z.string(),
|
||||
success: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowImportResponseSchema = z.object({
|
||||
workflowId: z.string(),
|
||||
name: z.string(),
|
||||
success: z.literal(true),
|
||||
})
|
||||
|
||||
export const adminV1DeploymentVersionSchema = z.object({
|
||||
id: z.string(),
|
||||
version: z.number(),
|
||||
name: z.string().nullable(),
|
||||
isActive: z.boolean(),
|
||||
createdAt: z.string(),
|
||||
createdBy: z.string().nullable(),
|
||||
deployedByName: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const adminV1DeployResultSchema = z.object({
|
||||
isDeployed: z.literal(true),
|
||||
version: z.number(),
|
||||
deployedAt: z.string(),
|
||||
warnings: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export const adminV1UndeployResultSchema = z.object({
|
||||
isDeployed: z.literal(false),
|
||||
warnings: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export const adminV1ExportWorkflowsBodySchema = z.object({
|
||||
ids: z
|
||||
.array(z.string(), { error: 'ids must be a non-empty array of workflow IDs' })
|
||||
.nonempty({ error: 'ids must be a non-empty array of workflow IDs' }),
|
||||
})
|
||||
|
||||
export const adminV1WorkflowImportBodySchema = z.object({
|
||||
workspaceId: z
|
||||
.string({ error: 'workspaceId is required' })
|
||||
.min(1, { error: 'workspaceId is required' }),
|
||||
folderId: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
workflow: z.union([
|
||||
z.string({ error: 'workflow is required' }).min(1, { error: 'workflow is required' }),
|
||||
z.record(z.string(), z.unknown()),
|
||||
]),
|
||||
})
|
||||
|
||||
const adminV1DeleteWorkflowResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
workflowId: z.string(),
|
||||
})
|
||||
|
||||
const adminV1WorkflowVersionsResultSchema = z.object({
|
||||
versions: z.array(adminV1DeploymentVersionSchema),
|
||||
})
|
||||
|
||||
const adminV1ActivateWorkflowVersionResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
version: z.number(),
|
||||
deployedAt: z.string(),
|
||||
warnings: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export const adminV1ListWorkflowsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workflows',
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1WorkflowSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetWorkflowContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workflows/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkflowDetailSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1DeleteWorkflowContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/workflows/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1DeleteWorkflowResultSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1DeployWorkflowContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/workflows/[id]/deploy',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1DeployResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1UndeployWorkflowContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/workflows/[id]/deploy',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1UndeployResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ListWorkflowVersionsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workflows/[id]/versions',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkflowVersionsResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ActivateWorkflowVersionContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/workflows/[id]/versions/[versionId]/activate',
|
||||
params: adminV1WorkflowVersionParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1ActivateWorkflowVersionResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ExportWorkflowContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workflows/[id]/export',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkflowExportPayloadSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ExportWorkflowsContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/workflows/export',
|
||||
query: adminV1ExportFormatQuerySchema,
|
||||
body: adminV1ExportWorkflowsBodySchema,
|
||||
response: {
|
||||
mode: 'binary',
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ImportWorkflowContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/workflows/import',
|
||||
body: adminV1WorkflowImportBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1WorkflowImportResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1WorkflowVersionParamsInput = z.input<typeof adminV1WorkflowVersionParamsSchema>
|
||||
export type AdminV1WorkflowVersionParams = z.output<typeof adminV1WorkflowVersionParamsSchema>
|
||||
export type AdminV1ExportWorkflowsBodyInput = z.input<typeof adminV1ExportWorkflowsBodySchema>
|
||||
export type AdminV1ExportWorkflowsBody = z.output<typeof adminV1ExportWorkflowsBodySchema>
|
||||
export type AdminV1WorkflowImportBodyInput = z.input<typeof adminV1WorkflowImportBodySchema>
|
||||
export type AdminV1WorkflowImportBody = z.output<typeof adminV1WorkflowImportBodySchema>
|
||||
export type AdminV1Workflow = z.output<typeof adminV1WorkflowSchema>
|
||||
export type AdminV1WorkflowDetail = z.output<typeof adminV1WorkflowDetailSchema>
|
||||
export type AdminV1WorkflowVariable = z.output<typeof adminV1WorkflowVariableSchema>
|
||||
export type AdminV1WorkflowExportState = z.output<typeof adminV1WorkflowExportStateSchema>
|
||||
export type AdminV1WorkflowExportPayload = z.output<typeof adminV1WorkflowExportPayloadSchema>
|
||||
export type AdminV1FolderExportPayload = z.output<typeof adminV1FolderExportPayloadSchema>
|
||||
export type AdminV1WorkspaceExportPayload = z.output<typeof adminV1WorkspaceExportPayloadSchema>
|
||||
export type AdminV1FolderFullExportPayload = z.output<typeof adminV1FolderFullExportPayloadSchema>
|
||||
export type AdminV1ImportResult = z.output<typeof adminV1ImportResultSchema>
|
||||
export type AdminV1WorkflowImportResponseBody = z.output<typeof adminV1WorkflowImportResponseSchema>
|
||||
export type AdminV1DeploymentVersion = z.output<typeof adminV1DeploymentVersionSchema>
|
||||
export type AdminV1DeployResult = z.output<typeof adminV1DeployResultSchema>
|
||||
export type AdminV1UndeployResult = z.output<typeof adminV1UndeployResultSchema>
|
||||
export type AdminV1ListWorkflowsResponse = ContractJsonResponse<typeof adminV1ListWorkflowsContract>
|
||||
export type AdminV1GetWorkflowResponse = ContractJsonResponse<typeof adminV1GetWorkflowContract>
|
||||
export type AdminV1DeleteWorkflowResponse = ContractJsonResponse<
|
||||
typeof adminV1DeleteWorkflowContract
|
||||
>
|
||||
export type AdminV1DeployWorkflowResponse = ContractJsonResponse<
|
||||
typeof adminV1DeployWorkflowContract
|
||||
>
|
||||
export type AdminV1UndeployWorkflowResponse = ContractJsonResponse<
|
||||
typeof adminV1UndeployWorkflowContract
|
||||
>
|
||||
export type AdminV1ListWorkflowVersionsResponse = ContractJsonResponse<
|
||||
typeof adminV1ListWorkflowVersionsContract
|
||||
>
|
||||
export type AdminV1ActivateWorkflowVersionResponse = ContractJsonResponse<
|
||||
typeof adminV1ActivateWorkflowVersionContract
|
||||
>
|
||||
export type AdminV1ExportWorkflowResponse = ContractJsonResponse<
|
||||
typeof adminV1ExportWorkflowContract
|
||||
>
|
||||
export type AdminV1ImportWorkflowResponse = ContractJsonResponse<
|
||||
typeof adminV1ImportWorkflowContract
|
||||
>
|
||||
@@ -0,0 +1,332 @@
|
||||
import { z } from 'zod'
|
||||
import { type ContractJsonResponse, defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import {
|
||||
adminV1ExportFormatQuerySchema,
|
||||
adminV1IdParamsSchema,
|
||||
adminV1ListResponseSchema,
|
||||
adminV1PaginationQuerySchema,
|
||||
adminV1QueryStringSchema,
|
||||
adminV1SingleResponseSchema,
|
||||
lastQueryValue,
|
||||
} from '@/lib/api/contracts/v1/admin/shared'
|
||||
import {
|
||||
adminV1ImportResultSchema,
|
||||
adminV1WorkflowSchema,
|
||||
} from '@/lib/api/contracts/v1/admin/workflows'
|
||||
import { workspacePermissionSchema } from '@/lib/api/contracts/workspaces'
|
||||
|
||||
export const adminV1WorkspaceMemberParamsSchema = adminV1IdParamsSchema.extend({
|
||||
memberId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const adminV1DeleteWorkspaceMemberQuerySchema = z.object({
|
||||
userId: z.preprocess(
|
||||
lastQueryValue,
|
||||
z
|
||||
.string({ error: 'userId query parameter is required' })
|
||||
.min(1, { error: 'userId query parameter is required' })
|
||||
),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceImportQuerySchema = z.object({
|
||||
createFolders: z
|
||||
.preprocess(lastQueryValue, z.enum(['true', 'false']).catch('true'))
|
||||
.transform((value) => value !== 'false'),
|
||||
rootFolderName: adminV1QueryStringSchema,
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
ownerId: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceDetailSchema = adminV1WorkspaceSchema.extend({
|
||||
workflowCount: z.number(),
|
||||
folderCount: z.number(),
|
||||
})
|
||||
|
||||
export const adminV1FolderSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
parentId: z.string().nullable(),
|
||||
color: z.string().nullable(),
|
||||
sortOrder: z.number(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceMemberSchema = z.object({
|
||||
id: z.string(),
|
||||
workspaceId: z.string(),
|
||||
userId: z.string(),
|
||||
permissions: workspacePermissionSchema,
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
userName: z.string(),
|
||||
userEmail: z.string(),
|
||||
userImage: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceImportResponseSchema = z.object({
|
||||
imported: z.number(),
|
||||
failed: z.number(),
|
||||
results: z.array(adminV1ImportResultSchema),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceMemberBodySchema = z.object({
|
||||
userId: z.string({ error: 'userId is required' }).min(1, { error: 'userId is required' }),
|
||||
permissions: workspacePermissionSchema.refine((value) => value !== null, {
|
||||
error: 'permissions must be "admin", "write", or "read"',
|
||||
}),
|
||||
})
|
||||
|
||||
export const adminV1UpdateWorkspaceMemberBodySchema = z.object({
|
||||
permissions: workspacePermissionSchema.refine((value) => value !== null, {
|
||||
error: 'permissions must be "admin", "write", or "read"',
|
||||
}),
|
||||
})
|
||||
|
||||
export const adminV1WorkspaceImportBodySchema = z.object({
|
||||
workflows: z.array(
|
||||
z.object({
|
||||
content: z.union([z.string(), z.record(z.string(), z.unknown())]),
|
||||
name: z.string().optional(),
|
||||
folderPath: z.array(z.string()).optional(),
|
||||
}),
|
||||
{ error: 'Invalid JSON body. Expected { workflows: [...] }' }
|
||||
),
|
||||
})
|
||||
|
||||
const adminV1DeleteWorkspaceWorkflowsResultSchema = z.object({
|
||||
success: z.literal(true),
|
||||
deleted: z.number(),
|
||||
})
|
||||
|
||||
const adminV1WorkspaceMemberMutationResultSchema = adminV1WorkspaceMemberSchema.extend({
|
||||
action: z.enum(['created', 'updated', 'already_member']),
|
||||
})
|
||||
|
||||
const adminV1DeleteWorkspaceMemberResultSchema = z.object({
|
||||
removed: z.literal(true),
|
||||
userId: z.string(),
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
|
||||
const adminV1RemoveWorkspaceMemberResultSchema = z.object({
|
||||
removed: z.literal(true),
|
||||
memberId: z.string(),
|
||||
userId: z.string(),
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
|
||||
export const adminV1ListWorkspacesContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces',
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1WorkspaceSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetWorkspaceContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces/[id]',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkspaceDetailSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ListWorkspaceWorkflowsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces/[id]/workflows',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1WorkflowSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1DeleteWorkspaceWorkflowsContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/workspaces/[id]/workflows',
|
||||
params: adminV1IdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1DeleteWorkspaceWorkflowsResultSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ListWorkspaceFoldersContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces/[id]/folders',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1FolderSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ExportWorkspaceContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces/[id]/export',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1ExportFormatQuerySchema,
|
||||
response: {
|
||||
mode: 'binary',
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ImportWorkspaceContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/workspaces/[id]/import',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1WorkspaceImportQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1WorkspaceImportResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ListWorkspaceMembersContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces/[id]/members',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1PaginationQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1ListResponseSchema(adminV1WorkspaceMemberSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1CreateWorkspaceMemberContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/admin/workspaces/[id]/members',
|
||||
params: adminV1IdParamsSchema,
|
||||
body: adminV1WorkspaceMemberBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkspaceMemberMutationResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1DeleteWorkspaceMemberContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/workspaces/[id]/members',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1DeleteWorkspaceMemberQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1DeleteWorkspaceMemberResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1GetWorkspaceMemberContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/workspaces/[id]/members/[memberId]',
|
||||
params: adminV1WorkspaceMemberParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkspaceMemberSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1UpdateWorkspaceMemberContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/admin/workspaces/[id]/members/[memberId]',
|
||||
params: adminV1WorkspaceMemberParamsSchema,
|
||||
body: adminV1UpdateWorkspaceMemberBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1WorkspaceMemberSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1RemoveWorkspaceMemberContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/admin/workspaces/[id]/members/[memberId]',
|
||||
params: adminV1WorkspaceMemberParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: adminV1SingleResponseSchema(adminV1RemoveWorkspaceMemberResultSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const adminV1ExportFolderContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/folders/[id]/export',
|
||||
params: adminV1IdParamsSchema,
|
||||
query: adminV1ExportFormatQuerySchema,
|
||||
response: {
|
||||
mode: 'binary',
|
||||
},
|
||||
})
|
||||
|
||||
export type AdminV1WorkspaceMemberParamsInput = z.input<typeof adminV1WorkspaceMemberParamsSchema>
|
||||
export type AdminV1WorkspaceMemberParams = z.output<typeof adminV1WorkspaceMemberParamsSchema>
|
||||
export type AdminV1DeleteWorkspaceMemberQueryInput = z.input<
|
||||
typeof adminV1DeleteWorkspaceMemberQuerySchema
|
||||
>
|
||||
export type AdminV1DeleteWorkspaceMemberQuery = z.output<
|
||||
typeof adminV1DeleteWorkspaceMemberQuerySchema
|
||||
>
|
||||
export type AdminV1WorkspaceImportQueryInput = z.input<typeof adminV1WorkspaceImportQuerySchema>
|
||||
export type AdminV1WorkspaceImportQuery = z.output<typeof adminV1WorkspaceImportQuerySchema>
|
||||
export type AdminV1WorkspaceMemberBodyInput = z.input<typeof adminV1WorkspaceMemberBodySchema>
|
||||
export type AdminV1WorkspaceMemberBody = z.output<typeof adminV1WorkspaceMemberBodySchema>
|
||||
export type AdminV1UpdateWorkspaceMemberBodyInput = z.input<
|
||||
typeof adminV1UpdateWorkspaceMemberBodySchema
|
||||
>
|
||||
export type AdminV1UpdateWorkspaceMemberBody = z.output<
|
||||
typeof adminV1UpdateWorkspaceMemberBodySchema
|
||||
>
|
||||
export type AdminV1WorkspaceImportBodyInput = z.input<typeof adminV1WorkspaceImportBodySchema>
|
||||
export type AdminV1WorkspaceImportBody = z.output<typeof adminV1WorkspaceImportBodySchema>
|
||||
export type AdminV1Workspace = z.output<typeof adminV1WorkspaceSchema>
|
||||
export type AdminV1WorkspaceDetail = z.output<typeof adminV1WorkspaceDetailSchema>
|
||||
export type AdminV1Folder = z.output<typeof adminV1FolderSchema>
|
||||
export type AdminV1WorkspaceMember = z.output<typeof adminV1WorkspaceMemberSchema>
|
||||
export type AdminV1WorkspaceImportResponseBody = z.output<
|
||||
typeof adminV1WorkspaceImportResponseSchema
|
||||
>
|
||||
export type AdminV1ListWorkspacesResponse = ContractJsonResponse<
|
||||
typeof adminV1ListWorkspacesContract
|
||||
>
|
||||
export type AdminV1GetWorkspaceResponse = ContractJsonResponse<typeof adminV1GetWorkspaceContract>
|
||||
export type AdminV1ListWorkspaceWorkflowsResponse = ContractJsonResponse<
|
||||
typeof adminV1ListWorkspaceWorkflowsContract
|
||||
>
|
||||
export type AdminV1DeleteWorkspaceWorkflowsResponse = ContractJsonResponse<
|
||||
typeof adminV1DeleteWorkspaceWorkflowsContract
|
||||
>
|
||||
export type AdminV1ListWorkspaceFoldersResponse = ContractJsonResponse<
|
||||
typeof adminV1ListWorkspaceFoldersContract
|
||||
>
|
||||
export type AdminV1ImportWorkspaceResponse = ContractJsonResponse<
|
||||
typeof adminV1ImportWorkspaceContract
|
||||
>
|
||||
export type AdminV1ListWorkspaceMembersResponse = ContractJsonResponse<
|
||||
typeof adminV1ListWorkspaceMembersContract
|
||||
>
|
||||
export type AdminV1CreateWorkspaceMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1CreateWorkspaceMemberContract
|
||||
>
|
||||
export type AdminV1DeleteWorkspaceMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1DeleteWorkspaceMemberContract
|
||||
>
|
||||
export type AdminV1GetWorkspaceMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1GetWorkspaceMemberContract
|
||||
>
|
||||
export type AdminV1UpdateWorkspaceMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1UpdateWorkspaceMemberContract
|
||||
>
|
||||
export type AdminV1RemoveWorkspaceMemberResponse = ContractJsonResponse<
|
||||
typeof adminV1RemoveWorkspaceMemberContract
|
||||
>
|
||||
@@ -0,0 +1,104 @@
|
||||
import { z } from 'zod'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
|
||||
const isoDateString = z.string().refine((value) => !Number.isNaN(Date.parse(value)), {
|
||||
error: 'Invalid date format. Use ISO 8601.',
|
||||
})
|
||||
|
||||
const optionalQueryString = z.preprocess(
|
||||
(value) => (value === '' ? undefined : value),
|
||||
z.string().optional()
|
||||
)
|
||||
|
||||
export const v1AuditLogParamsSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
})
|
||||
|
||||
export const v1ListAuditLogsQuerySchema = z.object({
|
||||
action: z.string().optional(),
|
||||
resourceType: z.string().optional(),
|
||||
resourceId: z.string().optional(),
|
||||
workspaceId: z.string().optional(),
|
||||
actorId: z.string().optional(),
|
||||
startDate: isoDateString.optional(),
|
||||
endDate: isoDateString.optional(),
|
||||
includeDeparted: z
|
||||
.enum(['true', 'false'])
|
||||
.transform((value) => value === 'true')
|
||||
.optional()
|
||||
.default(false),
|
||||
limit: z.coerce.number().min(1).max(100).optional().default(50),
|
||||
cursor: z.string().optional(),
|
||||
})
|
||||
|
||||
export type V1ListAuditLogsQuery = z.output<typeof v1ListAuditLogsQuerySchema>
|
||||
export type V1AuditLogParams = z.output<typeof v1AuditLogParamsSchema>
|
||||
|
||||
export const v1AdminAuditLogsQuerySchema = z.object({
|
||||
action: optionalQueryString,
|
||||
resourceType: optionalQueryString,
|
||||
resourceId: optionalQueryString,
|
||||
workspaceId: optionalQueryString,
|
||||
actorId: optionalQueryString,
|
||||
actorEmail: optionalQueryString,
|
||||
startDate: z.preprocess((value) => (value === '' ? undefined : value), isoDateString.optional()),
|
||||
endDate: z.preprocess((value) => (value === '' ? undefined : value), isoDateString.optional()),
|
||||
})
|
||||
|
||||
/**
|
||||
* Generic wrapper used by v1 admin audit-log responses. The `data` and
|
||||
* `limits` halves are intentionally `z.unknown()` because this proxy returns
|
||||
* provider-shaped payloads that vary per route family; tightening here would
|
||||
* require a discriminated union per route, which is tracked as a follow-up.
|
||||
*
|
||||
* boundary-policy: this is the "validates nothing" alias form that the audit
|
||||
* script's `untyped-response` regex doesn't currently catch. Treat any new
|
||||
* wrapper of this shape the same way and either annotate at the contract use
|
||||
* site with `// untyped-response: <reason>` or replace with a concrete schema.
|
||||
*/
|
||||
const apiResponseWithLimitsSchema = z
|
||||
.object({
|
||||
data: z.unknown(),
|
||||
limits: z.unknown().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const v1ListAuditLogsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/audit-logs',
|
||||
query: v1ListAuditLogsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: apiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetAuditLogContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/audit-logs/[id]',
|
||||
params: v1AuditLogParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: apiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1AdminListAuditLogsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/audit-logs',
|
||||
query: v1AdminAuditLogsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: apiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1AdminGetAuditLogContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/admin/audit-logs/[id]',
|
||||
params: v1AuditLogParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: apiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { z } from 'zod'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import { COPILOT_REQUEST_MODES } from '@/lib/copilot/constants'
|
||||
|
||||
export const v1CopilotChatBodySchema = z.object({
|
||||
message: z.string().min(1, 'message is required'),
|
||||
workflowId: z.string().optional(),
|
||||
workflowName: z.string().optional(),
|
||||
chatId: z.string().optional(),
|
||||
mode: z.enum(COPILOT_REQUEST_MODES).optional().default('agent'),
|
||||
model: z.string().optional(),
|
||||
autoExecuteTools: z.boolean().optional().default(true),
|
||||
timeout: z.number().int().min(1000).max(3_600_000).optional().default(3_600_000),
|
||||
})
|
||||
|
||||
export type V1CopilotChatBody = z.output<typeof v1CopilotChatBodySchema>
|
||||
|
||||
const v1CopilotChatToolCallSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
status: z.string(),
|
||||
params: z.record(z.string(), z.unknown()).optional(),
|
||||
// untyped-response: copilot tool result is the user-defined output of an arbitrary tool invocation
|
||||
result: z.unknown().optional(),
|
||||
error: z.string().optional(),
|
||||
durationMs: z.number().optional(),
|
||||
})
|
||||
|
||||
export const v1CopilotChatContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/copilot/chat',
|
||||
body: v1CopilotChatBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: z.object({
|
||||
success: z.boolean(),
|
||||
content: z.string().optional(),
|
||||
toolCalls: z.array(v1CopilotChatToolCallSchema).optional(),
|
||||
chatId: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import { z } from 'zod'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
|
||||
export const v1FileParamsSchema = z.object({
|
||||
fileId: z.string({ error: 'File ID is required' }).min(1, 'File ID is required'),
|
||||
})
|
||||
|
||||
export const v1WorkspaceIdQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'workspaceId query parameter is required'),
|
||||
})
|
||||
|
||||
export const v1UploadFileFormFieldsSchema = z.object({
|
||||
workspaceId: z.string().min(1, 'workspaceId form field is required'),
|
||||
})
|
||||
|
||||
export type V1FileParams = z.output<typeof v1FileParamsSchema>
|
||||
export type V1WorkspaceIdQuery = z.output<typeof v1WorkspaceIdQuerySchema>
|
||||
export type V1UploadFileFormFields = z.output<typeof v1UploadFileFormFieldsSchema>
|
||||
|
||||
const v1FilesResponseSchema = z
|
||||
.object({
|
||||
success: z.boolean().optional(),
|
||||
data: z.unknown().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const v1ListFilesContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/files',
|
||||
query: v1WorkspaceIdQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1FilesResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UploadFileContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/files',
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1FilesResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DownloadFileContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/files/[fileId]',
|
||||
params: v1FileParamsSchema,
|
||||
query: v1WorkspaceIdQuerySchema,
|
||||
response: {
|
||||
mode: 'binary',
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteFileContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/files/[fileId]',
|
||||
params: v1FileParamsSchema,
|
||||
query: v1WorkspaceIdQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1FilesResponseSchema,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from '@/lib/api/contracts/v1/admin'
|
||||
export * from '@/lib/api/contracts/v1/audit-logs'
|
||||
export * from '@/lib/api/contracts/v1/copilot'
|
||||
export * from '@/lib/api/contracts/v1/files'
|
||||
export * from '@/lib/api/contracts/v1/knowledge'
|
||||
export * from '@/lib/api/contracts/v1/logs'
|
||||
export * from '@/lib/api/contracts/v1/tables'
|
||||
export * from '@/lib/api/contracts/v1/workflows'
|
||||
@@ -0,0 +1,260 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
knowledgeBaseParamsSchema,
|
||||
knowledgeDocumentParamsSchema,
|
||||
successResponseSchema,
|
||||
} from '@/lib/api/contracts/knowledge/shared'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import { KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH } from '@/lib/knowledge/constants'
|
||||
|
||||
/**
|
||||
* Public API v1 schemas (`/api/v1/knowledge/**`)
|
||||
*
|
||||
* The public API surface intentionally diverges from the in-app contracts:
|
||||
* - `workspaceId` is REQUIRED (used for tenant scoping + rate-limiting), not
|
||||
* inferred from session.
|
||||
* - Chunking config is the simple three-number form (no `strategy` knob).
|
||||
* - Names/descriptions have explicit length caps.
|
||||
* - List queries default to sensible values (`enabledFilter='all'`,
|
||||
* `sortBy='uploadedAt'`, `sortOrder='desc'`).
|
||||
*
|
||||
* Embedding model/dimension are fixed server-side and not accepted as input.
|
||||
*/
|
||||
|
||||
/** Simpler chunking config used by the public API (no `strategy`). */
|
||||
export const v1ChunkingConfigSchema = z.object({
|
||||
maxSize: z.number().min(100).max(4000).default(1024),
|
||||
minSize: z.number().min(1).max(2000).default(100),
|
||||
overlap: z.number().min(0).max(500).default(200),
|
||||
})
|
||||
|
||||
/** GET `/api/v1/knowledge` — list knowledge bases scoped to a workspace. */
|
||||
export const v1ListKnowledgeBasesQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'workspaceId query parameter is required'),
|
||||
})
|
||||
|
||||
/** POST `/api/v1/knowledge` — create a knowledge base. */
|
||||
export const v1CreateKnowledgeBaseBodySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
name: z.string().min(1, 'Name is required').max(255, 'Name must be 255 characters or less'),
|
||||
description: z
|
||||
.string()
|
||||
.max(
|
||||
KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH,
|
||||
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
)
|
||||
.optional(),
|
||||
chunkingConfig: v1ChunkingConfigSchema.optional().default({
|
||||
maxSize: 1024,
|
||||
minSize: 100,
|
||||
overlap: 200,
|
||||
}),
|
||||
})
|
||||
|
||||
/** GET/DELETE `/api/v1/knowledge/[id]` — workspace scope param. */
|
||||
export const v1KnowledgeWorkspaceQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'workspaceId query parameter is required'),
|
||||
})
|
||||
|
||||
/** PUT `/api/v1/knowledge/[id]` — partial update with workspace scope in body. */
|
||||
export const v1UpdateKnowledgeBaseBodySchema = z
|
||||
.object({
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
name: z.string().min(1).max(255, 'Name must be 255 characters or less').optional(),
|
||||
description: z
|
||||
.string()
|
||||
.max(
|
||||
KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH,
|
||||
`Description must be ${KNOWLEDGE_BASE_DESCRIPTION_MAX_LENGTH} characters or less`
|
||||
)
|
||||
.optional(),
|
||||
chunkingConfig: z
|
||||
.object({
|
||||
maxSize: z.number().min(100).max(4000),
|
||||
minSize: z.number().min(1).max(2000),
|
||||
overlap: z.number().min(0).max(500),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
data.name !== undefined ||
|
||||
data.description !== undefined ||
|
||||
data.chunkingConfig !== undefined,
|
||||
{ message: 'At least one of name, description, or chunkingConfig must be provided' }
|
||||
)
|
||||
|
||||
/** GET `/api/v1/knowledge/[id]/documents` — list documents (defaults differ from in-app list). */
|
||||
export const v1ListKnowledgeDocumentsQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'workspaceId query parameter is required'),
|
||||
limit: z.coerce.number().int().min(1).max(100).default(50),
|
||||
offset: z.coerce.number().int().min(0).default(0),
|
||||
search: z.string().optional(),
|
||||
enabledFilter: z.enum(['all', 'enabled', 'disabled']).default('all'),
|
||||
sortBy: z
|
||||
.enum([
|
||||
'filename',
|
||||
'fileSize',
|
||||
'tokenCount',
|
||||
'chunkCount',
|
||||
'uploadedAt',
|
||||
'processingStatus',
|
||||
'enabled',
|
||||
])
|
||||
.default('uploadedAt'),
|
||||
sortOrder: z.enum(['asc', 'desc']).default('desc'),
|
||||
})
|
||||
|
||||
/**
|
||||
* POST `/api/v1/knowledge/search` tag filter — uses display `tagName` (not
|
||||
* slot) and a default operator. Distinct from the in-app
|
||||
* `documentTagFilterSchema`, which is slot-based and used for list filtering.
|
||||
*/
|
||||
export const v1SearchTagFilterSchema = z.object({
|
||||
tagName: z.string(),
|
||||
fieldType: z.enum(['text', 'number', 'date', 'boolean']).optional(),
|
||||
operator: z.string().default('eq'),
|
||||
value: z.union([z.string(), z.number(), z.boolean()]),
|
||||
valueTo: z.union([z.string(), z.number()]).optional(),
|
||||
})
|
||||
|
||||
/** POST `/api/v1/knowledge/search` body. */
|
||||
export const v1KnowledgeSearchBodySchema = z
|
||||
.object({
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
knowledgeBaseIds: z.union([
|
||||
z.string().min(1, 'Knowledge base ID is required'),
|
||||
z
|
||||
.array(z.string().min(1))
|
||||
.min(1, 'At least one knowledge base ID is required')
|
||||
.max(20, 'Maximum 20 knowledge base IDs allowed'),
|
||||
]),
|
||||
query: z.string().optional(),
|
||||
topK: z.number().min(1).max(100).default(10),
|
||||
tagFilters: z.array(v1SearchTagFilterSchema).optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const hasQuery = data.query && data.query.trim().length > 0
|
||||
const hasTagFilters = data.tagFilters && data.tagFilters.length > 0
|
||||
return hasQuery || hasTagFilters
|
||||
},
|
||||
{
|
||||
message: 'Either query or tagFilters must be provided',
|
||||
}
|
||||
)
|
||||
|
||||
export type V1ListKnowledgeBasesQuery = z.output<typeof v1ListKnowledgeBasesQuerySchema>
|
||||
export type V1CreateKnowledgeBaseBody = z.output<typeof v1CreateKnowledgeBaseBodySchema>
|
||||
export type V1KnowledgeWorkspaceQuery = z.output<typeof v1KnowledgeWorkspaceQuerySchema>
|
||||
export type V1UpdateKnowledgeBaseBody = z.output<typeof v1UpdateKnowledgeBaseBodySchema>
|
||||
export type V1ListKnowledgeDocumentsQuery = z.output<typeof v1ListKnowledgeDocumentsQuerySchema>
|
||||
export type V1KnowledgeSearchBody = z.output<typeof v1KnowledgeSearchBodySchema>
|
||||
|
||||
const v1KnowledgeApiResponseSchema = successResponseSchema(z.unknown()).passthrough()
|
||||
|
||||
export const v1ListKnowledgeBasesContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/knowledge',
|
||||
query: v1ListKnowledgeBasesQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1CreateKnowledgeBaseContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/knowledge',
|
||||
body: v1CreateKnowledgeBaseBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetKnowledgeBaseContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/knowledge/[id]',
|
||||
params: knowledgeBaseParamsSchema,
|
||||
query: v1KnowledgeWorkspaceQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UpdateKnowledgeBaseContract = defineRouteContract({
|
||||
method: 'PUT',
|
||||
path: '/api/v1/knowledge/[id]',
|
||||
params: knowledgeBaseParamsSchema,
|
||||
body: v1UpdateKnowledgeBaseBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteKnowledgeBaseContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/knowledge/[id]',
|
||||
params: knowledgeBaseParamsSchema,
|
||||
query: v1KnowledgeWorkspaceQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1ListKnowledgeDocumentsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/knowledge/[id]/documents',
|
||||
params: knowledgeBaseParamsSchema,
|
||||
query: v1ListKnowledgeDocumentsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UploadKnowledgeDocumentContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/knowledge/[id]/documents',
|
||||
params: knowledgeBaseParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetKnowledgeDocumentContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/knowledge/[id]/documents/[documentId]',
|
||||
params: knowledgeDocumentParamsSchema,
|
||||
query: v1KnowledgeWorkspaceQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteKnowledgeDocumentContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/knowledge/[id]/documents/[documentId]',
|
||||
params: knowledgeDocumentParamsSchema,
|
||||
query: v1KnowledgeWorkspaceQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1KnowledgeSearchContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/knowledge/search',
|
||||
body: v1KnowledgeSearchBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1KnowledgeApiResponseSchema,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
import { z } from 'zod'
|
||||
import { booleanQueryFlagSchema } from '@/lib/api/contracts/primitives'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
|
||||
export const v1LogParamsSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
})
|
||||
|
||||
export const v1ExecutionParamsSchema = z.object({
|
||||
executionId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const v1ListLogsQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1),
|
||||
workflowIds: z.string().optional(),
|
||||
folderIds: z.string().optional(),
|
||||
triggers: z.string().optional(),
|
||||
level: z.enum(['info', 'error']).optional(),
|
||||
startDate: z.string().optional(),
|
||||
endDate: z.string().optional(),
|
||||
executionId: z.string().optional(),
|
||||
minDurationMs: z.coerce.number().optional(),
|
||||
maxDurationMs: z.coerce.number().optional(),
|
||||
minCost: z.coerce.number().optional(),
|
||||
maxCost: z.coerce.number().optional(),
|
||||
model: z.string().optional(),
|
||||
details: z.enum(['basic', 'full']).optional().default('basic'),
|
||||
includeTraceSpans: booleanQueryFlagSchema.optional().default(false),
|
||||
includeFinalOutput: booleanQueryFlagSchema.optional().default(false),
|
||||
// Clamp rather than reject: this limit was previously unbounded, so a hard
|
||||
// .max() would 400 existing consumers passing a larger value. Page size is
|
||||
// still capped to bound response size and materialization reads.
|
||||
limit: z.coerce
|
||||
.number()
|
||||
.optional()
|
||||
.default(100)
|
||||
.transform((v) => Math.min(Math.max(1, Math.trunc(v)), 1000)),
|
||||
cursor: z.string().optional(),
|
||||
order: z.enum(['desc', 'asc']).optional().default('desc'),
|
||||
})
|
||||
|
||||
const v1ApiResponseWithLimitsSchema = z
|
||||
.object({
|
||||
limits: z.unknown().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export type V1ListLogsQuery = z.output<typeof v1ListLogsQuerySchema>
|
||||
export type V1LogParams = z.output<typeof v1LogParamsSchema>
|
||||
export type V1ExecutionParams = z.output<typeof v1ExecutionParamsSchema>
|
||||
|
||||
export const v1ListLogsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/logs',
|
||||
query: v1ListLogsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1ApiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetLogContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/logs/[id]',
|
||||
params: v1LogParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1ApiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetExecutionContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/logs/executions/[executionId]',
|
||||
params: v1ExecutionParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1ApiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,271 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
createTableBodySchema,
|
||||
createTableColumnBodySchema,
|
||||
deleteTableColumnBodySchema,
|
||||
deleteTableRowsBodySchema,
|
||||
insertTableRowBodyBaseSchema,
|
||||
rowAnchorMutexRefine,
|
||||
rowDataSchema,
|
||||
tableIdParamsSchema,
|
||||
tableRowParamsSchema,
|
||||
tableRowsQueryBaseSchema,
|
||||
updateRowsByFilterBodySchema,
|
||||
updateTableColumnBodySchema,
|
||||
updateTableRowBodySchema,
|
||||
upsertTableRowBodySchema,
|
||||
} from '@/lib/api/contracts/tables'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import type { Filter, Sort } from '@/lib/table'
|
||||
import { TABLE_LIMITS } from '@/lib/table/constants'
|
||||
|
||||
const domainObjectSchema = <T>() => z.custom<T>(isRecordLike)
|
||||
|
||||
const optionalJsonObjectQuerySchema = <T>(label: string) =>
|
||||
z
|
||||
.union([z.string(), domainObjectSchema<T>()])
|
||||
.optional()
|
||||
.transform((value, ctx): T | undefined => {
|
||||
if (value === undefined || value === '') return undefined
|
||||
if (typeof value !== 'string') return value
|
||||
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(value)
|
||||
if (isRecordLike(parsed)) return parsed as T
|
||||
} catch {
|
||||
ctx.addIssue({ code: 'custom', message: `Invalid ${label} JSON` })
|
||||
return z.NEVER
|
||||
}
|
||||
|
||||
ctx.addIssue({ code: 'custom', message: `${label} must be a JSON object` })
|
||||
return z.NEVER
|
||||
})
|
||||
|
||||
export const v1TableRowsQuerySchema = tableRowsQueryBaseSchema.omit({ after: true }).extend({
|
||||
filter: optionalJsonObjectQuerySchema<Filter>('filter'),
|
||||
sort: optionalJsonObjectQuerySchema<Sort>('sort'),
|
||||
})
|
||||
|
||||
export const v1ListTablesQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'workspaceId query parameter is required'),
|
||||
})
|
||||
|
||||
export const v1CreateTableBodySchema = createTableBodySchema.omit({
|
||||
initialRowCount: true,
|
||||
})
|
||||
|
||||
/**
|
||||
* Public API insert row body — no caller-controlled `position`. Server places
|
||||
* new rows at the tail; ordering by index is an in-app affordance only.
|
||||
*/
|
||||
export const v1InsertTableRowBodySchema = insertTableRowBodyBaseSchema
|
||||
.omit({ position: true })
|
||||
.refine(...rowAnchorMutexRefine)
|
||||
|
||||
/**
|
||||
* Public API batch insert body — no `positions`. Same rationale as above.
|
||||
*/
|
||||
export const v1BatchInsertTableRowsBodySchema = z.object({
|
||||
workspaceId: z.string().min(1, 'Workspace ID is required'),
|
||||
rows: z
|
||||
.array(rowDataSchema)
|
||||
.min(1, 'At least one row is required')
|
||||
.max(
|
||||
TABLE_LIMITS.MAX_BATCH_INSERT_SIZE,
|
||||
`Cannot insert more than ${TABLE_LIMITS.MAX_BATCH_INSERT_SIZE} rows per batch`
|
||||
),
|
||||
})
|
||||
|
||||
export const v1CreateTableRowsBodySchema = z.union([
|
||||
v1BatchInsertTableRowsBodySchema,
|
||||
v1InsertTableRowBodySchema,
|
||||
])
|
||||
|
||||
export type V1ListTablesQuery = z.output<typeof v1ListTablesQuerySchema>
|
||||
export type V1TableRowsQuery = z.output<typeof v1TableRowsQuerySchema>
|
||||
export type V1InsertTableRowBody = z.output<typeof v1InsertTableRowBodySchema>
|
||||
export type V1BatchInsertTableRowsBody = z.output<typeof v1BatchInsertTableRowsBodySchema>
|
||||
export type V1CreateTableRowsBody = z.output<typeof v1CreateTableRowsBodySchema>
|
||||
|
||||
const successResponseSchema = <T extends z.ZodType>(dataSchema: T) =>
|
||||
z.object({
|
||||
success: z.literal(true),
|
||||
data: dataSchema,
|
||||
})
|
||||
|
||||
const v1TableApiResponseSchema = successResponseSchema(z.unknown()).passthrough()
|
||||
|
||||
export const v1ListTablesContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/tables',
|
||||
query: v1ListTablesQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1CreateTableContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/tables',
|
||||
body: v1CreateTableBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetTableContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/tables/[tableId]',
|
||||
params: tableIdParamsSchema,
|
||||
query: v1ListTablesQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteTableContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/tables/[tableId]',
|
||||
params: tableIdParamsSchema,
|
||||
query: v1ListTablesQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1AddTableColumnContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/tables/[tableId]/columns',
|
||||
params: tableIdParamsSchema,
|
||||
body: createTableColumnBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UpdateTableColumnContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/tables/[tableId]/columns',
|
||||
params: tableIdParamsSchema,
|
||||
body: updateTableColumnBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteTableColumnContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/tables/[tableId]/columns',
|
||||
params: tableIdParamsSchema,
|
||||
body: deleteTableColumnBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1ListTableRowsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/tables/[tableId]/rows',
|
||||
params: tableIdParamsSchema,
|
||||
query: v1TableRowsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1CreateTableRowContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/tables/[tableId]/rows',
|
||||
params: tableIdParamsSchema,
|
||||
body: v1CreateTableRowsBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1BatchCreateTableRowsContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/tables/[tableId]/rows',
|
||||
params: tableIdParamsSchema,
|
||||
body: v1BatchInsertTableRowsBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UpdateRowsByFilterContract = defineRouteContract({
|
||||
method: 'PUT',
|
||||
path: '/api/v1/tables/[tableId]/rows',
|
||||
params: tableIdParamsSchema,
|
||||
body: updateRowsByFilterBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteTableRowsContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/tables/[tableId]/rows',
|
||||
params: tableIdParamsSchema,
|
||||
body: deleteTableRowsBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetTableRowContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/tables/[tableId]/rows/[rowId]',
|
||||
params: tableRowParamsSchema,
|
||||
query: v1ListTablesQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UpdateTableRowContract = defineRouteContract({
|
||||
method: 'PATCH',
|
||||
path: '/api/v1/tables/[tableId]/rows/[rowId]',
|
||||
params: tableRowParamsSchema,
|
||||
body: updateTableRowBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1DeleteTableRowContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/tables/[tableId]/rows/[rowId]',
|
||||
params: tableRowParamsSchema,
|
||||
query: v1ListTablesQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UpsertTableRowContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/tables/[tableId]/rows/upsert',
|
||||
params: tableIdParamsSchema,
|
||||
body: upsertTableRowBodySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1TableApiResponseSchema,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,144 @@
|
||||
import { z } from 'zod'
|
||||
import { deploymentVersionMetadataFieldsSchema } from '@/lib/api/contracts/deployments'
|
||||
import { booleanQueryFlagSchema } from '@/lib/api/contracts/primitives'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import { workflowIdParamsSchema } from '@/lib/api/contracts/workflows'
|
||||
|
||||
export const v1ListWorkflowsQuerySchema = z.object({
|
||||
workspaceId: z.string().min(1),
|
||||
folderId: z.string().optional(),
|
||||
deployedOnly: booleanQueryFlagSchema.optional().default(false),
|
||||
limit: z.coerce.number().min(1).max(100).optional().default(50),
|
||||
cursor: z.string().optional(),
|
||||
})
|
||||
|
||||
export type V1ListWorkflowsQuery = z.output<typeof v1ListWorkflowsQuerySchema>
|
||||
|
||||
/**
|
||||
* Generic wrapper used by v1 admin workflow list/detail responses. `data` is
|
||||
* the provider-shaped admin payload (varies per route) and `limits` is an
|
||||
* optional rate-limit envelope; both are intentionally `z.unknown()` here.
|
||||
* Tightening would require per-route discriminated unions and is tracked as
|
||||
* a follow-up.
|
||||
*
|
||||
* boundary-policy: this is the "validates nothing" alias form that the audit
|
||||
* script's `untyped-response` regex doesn't currently catch. Treat any new
|
||||
* wrapper of this shape the same way — either annotate at the contract use
|
||||
* site with `// untyped-response: <reason>` or replace with a concrete schema.
|
||||
*/
|
||||
const v1WorkflowApiResponseWithLimitsSchema = z
|
||||
.object({
|
||||
data: z.unknown(),
|
||||
limits: z.unknown().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const v1ListWorkflowsContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/workflows',
|
||||
query: v1ListWorkflowsQuerySchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1WorkflowApiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
export const v1GetWorkflowContract = defineRouteContract({
|
||||
method: 'GET',
|
||||
path: '/api/v1/workflows/[id]',
|
||||
params: workflowIdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: v1WorkflowApiResponseWithLimitsSchema,
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Optional version metadata accepted by the v1 deploy endpoint. Field bounds
|
||||
* are shared with the UI deployment surface via
|
||||
* {@link deploymentVersionMetadataFieldsSchema}. The route tolerates an
|
||||
* absent/empty body, so this schema is validated by the handler against
|
||||
* `parseOptionalJsonBody` instead of being attached to the contract.
|
||||
*/
|
||||
export const v1DeployWorkflowBodySchema = z.object({
|
||||
name: deploymentVersionMetadataFieldsSchema.shape.name,
|
||||
description: deploymentVersionMetadataFieldsSchema.shape.description,
|
||||
})
|
||||
|
||||
export type V1DeployWorkflowBody = z.input<typeof v1DeployWorkflowBodySchema>
|
||||
|
||||
/** Bounded to the Postgres `integer` range of `workflow_deployment_version.version`. */
|
||||
const deploymentVersionNumberSchema = z
|
||||
.number()
|
||||
.int('version must be an integer')
|
||||
.min(1, 'version must be a positive integer')
|
||||
.max(2147483647, 'version is out of range')
|
||||
|
||||
/**
|
||||
* Optional rollback target accepted by the v1 rollback endpoint. When
|
||||
* `version` is omitted the route rolls back to the deployment version that
|
||||
* precedes the currently active one. Validated by the handler against
|
||||
* `parseOptionalJsonBody`, so it is not attached to the contract.
|
||||
*/
|
||||
export const v1RollbackWorkflowBodySchema = z.object({
|
||||
version: deploymentVersionNumberSchema.optional(),
|
||||
})
|
||||
|
||||
export type V1RollbackWorkflowBody = z.input<typeof v1RollbackWorkflowBodySchema>
|
||||
|
||||
const v1DeploymentStateSchema = z.object({
|
||||
id: z.string(),
|
||||
isDeployed: z.boolean(),
|
||||
deployedAt: z.string().nullable(),
|
||||
warnings: z.array(z.string()),
|
||||
})
|
||||
|
||||
export const v1DeployWorkflowDataSchema = v1DeploymentStateSchema.extend({
|
||||
version: z.number().optional(),
|
||||
})
|
||||
|
||||
export type V1DeployWorkflowData = z.output<typeof v1DeployWorkflowDataSchema>
|
||||
|
||||
export const v1RollbackWorkflowDataSchema = v1DeploymentStateSchema.extend({
|
||||
version: z.number(),
|
||||
})
|
||||
|
||||
export type V1RollbackWorkflowData = z.output<typeof v1RollbackWorkflowDataSchema>
|
||||
|
||||
export type V1UndeployWorkflowData = z.output<typeof v1DeploymentStateSchema>
|
||||
|
||||
const withV1Limits = <T extends z.ZodType>(data: T) =>
|
||||
z.object({
|
||||
data,
|
||||
limits: z.unknown().optional(),
|
||||
})
|
||||
|
||||
export const v1DeployWorkflowContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/workflows/[id]/deploy',
|
||||
params: workflowIdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: withV1Limits(v1DeployWorkflowDataSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const v1UndeployWorkflowContract = defineRouteContract({
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/workflows/[id]/deploy',
|
||||
params: workflowIdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: withV1Limits(v1DeploymentStateSchema),
|
||||
},
|
||||
})
|
||||
|
||||
export const v1RollbackWorkflowContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/v1/workflows/[id]/rollback',
|
||||
params: workflowIdParamsSchema,
|
||||
response: {
|
||||
mode: 'json',
|
||||
schema: withV1Limits(v1RollbackWorkflowDataSchema),
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user