d25d482dc2
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
615 lines
18 KiB
TypeScript
615 lines
18 KiB
TypeScript
import { z } from 'zod'
|
|
import {
|
|
type PiiRedactionSettings,
|
|
piiRedactionSettingsSchema,
|
|
retentionOverridesSchema,
|
|
workspaceIdSchema,
|
|
} from '@/lib/api/contracts/primitives'
|
|
import { organizationBillingDataSchema } from '@/lib/api/contracts/subscription'
|
|
import { defineRouteContract } from '@/lib/api/contracts/types'
|
|
import { workspacePermissionSchema } from '@/lib/api/contracts/workspaces'
|
|
import { HEX_COLOR_REGEX } from '@/lib/branding'
|
|
|
|
const booleanQueryParamSchema = z
|
|
.preprocess((value) => {
|
|
if (value === 'true') return true
|
|
if (value === 'false') return false
|
|
return value
|
|
}, z.boolean())
|
|
.optional()
|
|
|
|
const numericResponseSchema = z.preprocess((value) => {
|
|
if (typeof value !== 'string') return value
|
|
const parsed = Number.parseFloat(value)
|
|
return Number.isFinite(parsed) ? parsed : value
|
|
}, z.number())
|
|
|
|
export const organizationRoleSchema = z.enum(['owner', 'admin', 'member'], {
|
|
error: 'Invalid role',
|
|
})
|
|
export const organizationParamsSchema = z.object({
|
|
id: z.string().min(1),
|
|
})
|
|
|
|
export const organizationMemberParamsSchema = z.object({
|
|
id: z.string().min(1),
|
|
memberId: z.string().min(1),
|
|
})
|
|
|
|
export const organizationMemberQuerySchema = z
|
|
.object({
|
|
include: z.string().optional(),
|
|
})
|
|
.passthrough()
|
|
|
|
export const workspaceGrantSchema = z.object({
|
|
workspaceId: z.string().min(1),
|
|
permission: workspacePermissionSchema,
|
|
})
|
|
|
|
export const createOrganizationBodySchema = z
|
|
.object({
|
|
name: z.string().optional(),
|
|
slug: z.string().optional(),
|
|
})
|
|
.passthrough()
|
|
|
|
export const updateOrganizationBodySchema = z.object({
|
|
name: z.string().trim().min(1, 'Organization name is required').optional(),
|
|
slug: z
|
|
.string()
|
|
.trim()
|
|
.min(1, 'Organization slug is required')
|
|
.regex(
|
|
/^[a-z0-9-_]+$/,
|
|
'Slug can only contain lowercase letters, numbers, hyphens, and underscores'
|
|
)
|
|
.optional(),
|
|
logo: z.string().nullable().optional(),
|
|
})
|
|
|
|
export const createOrganizationInvitationBodySchema = z
|
|
.object({
|
|
email: z.string().optional(),
|
|
emails: z.array(z.string()).optional(),
|
|
role: z.enum(['member', 'admin'], { error: 'Invalid role' }).optional(),
|
|
workspaceInvitations: z.array(workspaceGrantSchema).optional(),
|
|
})
|
|
.passthrough()
|
|
|
|
export const organizationInvitationsQuerySchema = z
|
|
.object({
|
|
validate: booleanQueryParamSchema,
|
|
batch: booleanQueryParamSchema,
|
|
})
|
|
.passthrough()
|
|
|
|
export const updateOrganizationMemberRoleBodySchema = z.object({
|
|
role: organizationRoleSchema,
|
|
})
|
|
|
|
export const inviteOrganizationMemberBodySchema = z
|
|
.object({
|
|
email: z.string({ error: 'Email is required' }).min(1, 'Email is required'),
|
|
role: z.enum(['admin', 'member'], { error: 'Invalid role' }).optional(),
|
|
})
|
|
.passthrough()
|
|
|
|
const organizationDataRetentionHoursSchema = z
|
|
.number()
|
|
.int()
|
|
.min(24)
|
|
.max(43800)
|
|
.nullable()
|
|
.optional()
|
|
|
|
export type { PiiRedactionSettings }
|
|
|
|
export const updateOrganizationDataRetentionBodySchema = z.object({
|
|
logRetentionHours: organizationDataRetentionHoursSchema,
|
|
softDeleteRetentionHours: organizationDataRetentionHoursSchema,
|
|
taskCleanupHours: organizationDataRetentionHoursSchema,
|
|
piiRedaction: piiRedactionSettingsSchema.optional(),
|
|
retentionOverrides: retentionOverridesSchema.optional(),
|
|
})
|
|
|
|
export type UpdateOrganizationDataRetentionBody = z.input<
|
|
typeof updateOrganizationDataRetentionBodySchema
|
|
>
|
|
|
|
const organizationRetentionValuesSchema = z.object({
|
|
logRetentionHours: z.number().int().nullable(),
|
|
softDeleteRetentionHours: z.number().int().nullable(),
|
|
taskCleanupHours: z.number().int().nullable(),
|
|
piiRedaction: piiRedactionSettingsSchema.nullable(),
|
|
retentionOverrides: retentionOverridesSchema.nullable(),
|
|
})
|
|
|
|
export type OrganizationRetentionValues = z.output<typeof organizationRetentionValuesSchema>
|
|
|
|
const organizationDataRetentionDataSchema = z.object({
|
|
isEnterprise: z.boolean(),
|
|
defaults: organizationRetentionValuesSchema,
|
|
configured: organizationRetentionValuesSchema,
|
|
effective: organizationRetentionValuesSchema,
|
|
piiRedactionEnabled: z.boolean(),
|
|
piiGranularRedactionEnabled: z.boolean(),
|
|
})
|
|
|
|
export type OrganizationDataRetention = z.output<typeof organizationDataRetentionDataSchema>
|
|
|
|
export const organizationDataRetentionResponseSchema = z.object({
|
|
success: z.boolean(),
|
|
data: organizationDataRetentionDataSchema,
|
|
})
|
|
|
|
export const updateOrganizationWhitelabelBodySchema = z.object({
|
|
brandName: z
|
|
.string()
|
|
.trim()
|
|
.max(64, 'Brand name must be 64 characters or fewer')
|
|
.nullable()
|
|
.optional(),
|
|
logoUrl: z.string().min(1).nullable().optional(),
|
|
wordmarkUrl: z.string().min(1).nullable().optional(),
|
|
primaryColor: z
|
|
.string()
|
|
.regex(HEX_COLOR_REGEX, 'Primary color must be a valid hex color (e.g. #33c482)')
|
|
.nullable()
|
|
.optional(),
|
|
primaryHoverColor: z
|
|
.string()
|
|
.regex(HEX_COLOR_REGEX, 'Primary hover color must be a valid hex color')
|
|
.nullable()
|
|
.optional(),
|
|
accentColor: z
|
|
.string()
|
|
.regex(HEX_COLOR_REGEX, 'Accent color must be a valid hex color')
|
|
.nullable()
|
|
.optional(),
|
|
accentHoverColor: z
|
|
.string()
|
|
.regex(HEX_COLOR_REGEX, 'Accent hover color must be a valid hex color')
|
|
.nullable()
|
|
.optional(),
|
|
supportEmail: z
|
|
.string()
|
|
.email('Support email must be a valid email address')
|
|
.nullable()
|
|
.optional(),
|
|
documentationUrl: z.string().url('Documentation URL must be a valid URL').nullable().optional(),
|
|
termsUrl: z.string().url('Terms URL must be a valid URL').nullable().optional(),
|
|
privacyUrl: z.string().url('Privacy URL must be a valid URL').nullable().optional(),
|
|
hidePoweredBySim: z.boolean().optional(),
|
|
})
|
|
|
|
export const transferOwnershipBodySchema = z.object({
|
|
newOwnerUserId: z.string().min(1),
|
|
alsoLeave: z.boolean().optional().default(false),
|
|
})
|
|
|
|
export const rosterWorkspaceAccessSchema = z.object({
|
|
workspaceId: z.string(),
|
|
workspaceName: z.string(),
|
|
permission: workspacePermissionSchema,
|
|
})
|
|
|
|
export const rosterMemberSchema = z.object({
|
|
memberId: z.string(),
|
|
userId: z.string(),
|
|
role: z.enum(['owner', 'admin', 'member', 'external']),
|
|
createdAt: z.string(),
|
|
name: z.string(),
|
|
email: z.string(),
|
|
image: z.string().nullable(),
|
|
workspaces: z.array(rosterWorkspaceAccessSchema),
|
|
})
|
|
|
|
export const rosterPendingInvitationSchema = z.object({
|
|
id: z.string(),
|
|
email: z.string(),
|
|
role: z.string(),
|
|
kind: z.enum(['organization', 'workspace']),
|
|
membershipIntent: z.enum(['internal', 'external']).optional(),
|
|
createdAt: z.string(),
|
|
expiresAt: z.string(),
|
|
inviteeName: z.string().nullable(),
|
|
inviteeImage: z.string().nullable(),
|
|
workspaces: z.array(rosterWorkspaceAccessSchema),
|
|
})
|
|
|
|
export const organizationRosterSchema = z.object({
|
|
members: z.array(rosterMemberSchema),
|
|
pendingInvitations: z.array(rosterPendingInvitationSchema),
|
|
workspaces: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
})
|
|
|
|
export const organizationMemberUsageSchema = z
|
|
.object({
|
|
id: z.string(),
|
|
userId: z.string(),
|
|
organizationId: z.string(),
|
|
role: organizationRoleSchema,
|
|
createdAt: z.string(),
|
|
userName: z.string().nullable(),
|
|
userEmail: z.string().nullable(),
|
|
currentPeriodCost: numericResponseSchema.nullable().optional(),
|
|
currentUsageLimit: numericResponseSchema.nullable().optional(),
|
|
usageLimitUpdatedAt: z.string().nullable().optional(),
|
|
billingPeriodStart: z.string().nullable().optional(),
|
|
billingPeriodEnd: z.string().nullable().optional(),
|
|
})
|
|
.passthrough()
|
|
|
|
export const listOrganizationMembersResponseSchema = z
|
|
.object({
|
|
success: z.boolean(),
|
|
data: z.array(organizationMemberUsageSchema),
|
|
total: z.number(),
|
|
userRole: organizationRoleSchema,
|
|
hasAdminAccess: z.boolean(),
|
|
})
|
|
.passthrough()
|
|
|
|
const successResponseSchema = z
|
|
.object({
|
|
success: z.boolean(),
|
|
message: z.string().optional(),
|
|
})
|
|
.passthrough()
|
|
|
|
const organizationInvitationValidationResponseSchema = z
|
|
.object({
|
|
success: z.literal(true),
|
|
data: z.unknown(),
|
|
validatedBy: z.string(),
|
|
validatedAt: z.string(),
|
|
})
|
|
.passthrough()
|
|
|
|
export const getOrganizationRosterContract = defineRouteContract({
|
|
method: 'GET',
|
|
path: '/api/organizations/[id]/roster',
|
|
params: organizationParamsSchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: z.object({
|
|
success: z.boolean(),
|
|
data: organizationRosterSchema,
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const listOrganizationMembersContract = defineRouteContract({
|
|
method: 'GET',
|
|
path: '/api/organizations/[id]/members',
|
|
params: organizationParamsSchema,
|
|
query: organizationMemberQuerySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: listOrganizationMembersResponseSchema,
|
|
},
|
|
})
|
|
|
|
export const inviteOrganizationMemberContract = defineRouteContract({
|
|
method: 'POST',
|
|
path: '/api/organizations/[id]/members',
|
|
params: organizationParamsSchema,
|
|
body: inviteOrganizationMemberBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: successResponseSchema.extend({
|
|
data: z
|
|
.object({
|
|
invitationId: z.string(),
|
|
email: z.string(),
|
|
role: organizationRoleSchema,
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const inviteOrganizationMembersContract = defineRouteContract({
|
|
method: 'POST',
|
|
path: '/api/organizations/[id]/invitations',
|
|
params: organizationParamsSchema,
|
|
query: organizationInvitationsQuerySchema,
|
|
body: createOrganizationInvitationBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: z.union([
|
|
organizationInvitationValidationResponseSchema,
|
|
successResponseSchema.extend({
|
|
error: z.string().optional(),
|
|
data: z
|
|
.object({
|
|
invitationsSent: z.number(),
|
|
invitedEmails: z.array(z.string()),
|
|
directlyAdded: z.array(z.string()).optional(),
|
|
directlyAddedCount: z.number().optional(),
|
|
failedInvitations: z.array(z.object({ email: z.string(), error: z.string() })),
|
|
existingMembers: z.array(z.string()),
|
|
pendingInvitations: z.array(z.string()),
|
|
invalidEmails: z.array(z.string()),
|
|
workspaceGrantsPerInvite: z.number(),
|
|
seatInfo: z
|
|
.object({
|
|
seatsUsed: z.number(),
|
|
maxSeats: z.number(),
|
|
availableSeats: z.number(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
}),
|
|
]),
|
|
},
|
|
})
|
|
|
|
export const updateOrganizationMemberRoleContract = defineRouteContract({
|
|
method: 'PUT',
|
|
path: '/api/organizations/[id]/members/[memberId]',
|
|
params: organizationMemberParamsSchema,
|
|
body: updateOrganizationMemberRoleBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: successResponseSchema.extend({
|
|
data: z
|
|
.object({
|
|
id: z.string(),
|
|
userId: z.string(),
|
|
role: organizationRoleSchema,
|
|
updatedBy: z.string(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const removeOrganizationMemberContract = defineRouteContract({
|
|
method: 'DELETE',
|
|
path: '/api/organizations/[id]/members/[memberId]',
|
|
params: organizationMemberParamsSchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: successResponseSchema.extend({
|
|
data: z.record(z.string(), z.unknown()).optional(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
/** Per-member credit usage + cap for the Manage Credits modal (values in credits). */
|
|
export const organizationMemberUsageLimitDataSchema = z.object({
|
|
creditsUsed: z.number(),
|
|
creditLimit: z.number().nullable(),
|
|
/** Billing cadence of the org's subscription, so the UI can label the usage window. */
|
|
billingInterval: z.enum(['month', 'year']),
|
|
})
|
|
|
|
export const getOrganizationMemberUsageLimitContract = defineRouteContract({
|
|
method: 'GET',
|
|
path: '/api/organizations/[id]/members/[memberId]/usage-limit',
|
|
params: organizationMemberParamsSchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: z.object({
|
|
success: z.boolean(),
|
|
data: organizationMemberUsageLimitDataSchema,
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const updateOrganizationMemberUsageLimitBodySchema = z.object({
|
|
/** New cap in credits; `null` clears the per-member cap. */
|
|
creditLimit: z
|
|
.number()
|
|
.int('Credit limit must be a whole number of credits')
|
|
.min(0, 'Credit limit cannot be negative')
|
|
.nullable(),
|
|
})
|
|
|
|
export const updateOrganizationMemberUsageLimitContract = defineRouteContract({
|
|
method: 'PUT',
|
|
path: '/api/organizations/[id]/members/[memberId]/usage-limit',
|
|
params: organizationMemberParamsSchema,
|
|
body: updateOrganizationMemberUsageLimitBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: successResponseSchema.extend({
|
|
data: z
|
|
.object({
|
|
creditLimit: z.number().nullable(),
|
|
})
|
|
.optional(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
/**
|
|
* Self-service per-member usage for the chat-home credits chip. Values are in
|
|
* DOLLARS (the DB unit) so the client's `formatCredits` performs the single
|
|
* dollars→credits conversion — returning credits here would double-convert.
|
|
* `limitDollars` is null when no per-member cap applies (non-hosted, the
|
|
* workspace isn't org-owned, or no cap is set), so the chip falls back to the
|
|
* plan-level credits view.
|
|
*/
|
|
export const myMemberCreditsDataSchema = z.object({
|
|
usedDollars: z.number(),
|
|
limitDollars: z.number().nullable(),
|
|
})
|
|
export type MyMemberCreditsData = z.infer<typeof myMemberCreditsDataSchema>
|
|
|
|
/**
|
|
* Own-data-only (no admin gate, unlike the admin route above) and workspace-
|
|
* scoped, so the chat-home chip can resolve the acting member's own remaining.
|
|
*/
|
|
export const getMyMemberCreditsContract = defineRouteContract({
|
|
method: 'GET',
|
|
path: '/api/billing/member-credits',
|
|
query: z.object({ workspaceId: workspaceIdSchema }),
|
|
response: {
|
|
mode: 'json',
|
|
schema: z.object({
|
|
success: z.boolean(),
|
|
data: myMemberCreditsDataSchema,
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const transferOwnershipContract = defineRouteContract({
|
|
method: 'POST',
|
|
path: '/api/organizations/[id]/transfer-ownership',
|
|
params: organizationParamsSchema,
|
|
body: transferOwnershipBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: z
|
|
.object({
|
|
success: z.boolean(),
|
|
transferred: z.boolean(),
|
|
left: z.boolean(),
|
|
warning: z.string().optional(),
|
|
details: z.record(z.string(), z.unknown()).optional(),
|
|
})
|
|
.passthrough(),
|
|
},
|
|
})
|
|
|
|
export const updateOrganizationContract = defineRouteContract({
|
|
method: 'PUT',
|
|
path: '/api/organizations/[id]',
|
|
params: organizationParamsSchema,
|
|
body: updateOrganizationBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: successResponseSchema.extend({
|
|
data: z
|
|
.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
slug: z.string().nullable(),
|
|
logo: z.string().nullable(),
|
|
updatedAt: z.string(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const getOrganizationDataRetentionContract = defineRouteContract({
|
|
method: 'GET',
|
|
path: '/api/organizations/[id]/data-retention',
|
|
params: organizationParamsSchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: organizationDataRetentionResponseSchema,
|
|
},
|
|
})
|
|
|
|
export const updateOrganizationDataRetentionContract = defineRouteContract({
|
|
method: 'PUT',
|
|
path: '/api/organizations/[id]/data-retention',
|
|
params: organizationParamsSchema,
|
|
body: updateOrganizationDataRetentionBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: organizationDataRetentionResponseSchema,
|
|
},
|
|
})
|
|
|
|
// Read shape mirrors `OrganizationWhitelabelSettings` from
|
|
// `@/lib/branding/types`. All fields are optional (nullable on the way in
|
|
// for the PUT contract, but stored without nulls on the way out — the
|
|
// route deletes keys that are explicitly cleared).
|
|
export const organizationWhitelabelSettingsResponseSchema = z.object({
|
|
brandName: z.string().optional(),
|
|
logoUrl: z.string().optional(),
|
|
wordmarkUrl: z.string().optional(),
|
|
primaryColor: z.string().optional(),
|
|
primaryHoverColor: z.string().optional(),
|
|
accentColor: z.string().optional(),
|
|
accentHoverColor: z.string().optional(),
|
|
supportEmail: z.string().optional(),
|
|
documentationUrl: z.string().optional(),
|
|
termsUrl: z.string().optional(),
|
|
privacyUrl: z.string().optional(),
|
|
hidePoweredBySim: z.boolean().optional(),
|
|
})
|
|
|
|
const organizationWhitelabelEnvelopeResponseSchema = z.object({
|
|
success: z.boolean(),
|
|
data: organizationWhitelabelSettingsResponseSchema,
|
|
})
|
|
|
|
export const getOrganizationWhitelabelContract = defineRouteContract({
|
|
method: 'GET',
|
|
path: '/api/organizations/[id]/whitelabel',
|
|
params: organizationParamsSchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: organizationWhitelabelEnvelopeResponseSchema,
|
|
},
|
|
})
|
|
|
|
export const updateOrganizationWhitelabelContract = defineRouteContract({
|
|
method: 'PUT',
|
|
path: '/api/organizations/[id]/whitelabel',
|
|
params: organizationParamsSchema,
|
|
body: updateOrganizationWhitelabelBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: organizationWhitelabelEnvelopeResponseSchema,
|
|
},
|
|
})
|
|
|
|
export const createOrganizationContract = defineRouteContract({
|
|
method: 'POST',
|
|
path: '/api/organizations',
|
|
body: createOrganizationBodySchema,
|
|
response: {
|
|
mode: 'json',
|
|
schema: z.object({
|
|
success: z.boolean(),
|
|
organizationId: z.string(),
|
|
created: z.boolean(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
export const updateOrganizationUsageLimitContract = defineRouteContract({
|
|
method: 'PUT',
|
|
path: '/api/usage',
|
|
body: z.object({
|
|
context: z.literal('organization'),
|
|
organizationId: z.string().min(1),
|
|
limit: z.number().min(0, 'Limit must be a non-negative number'),
|
|
}),
|
|
response: {
|
|
mode: 'json',
|
|
schema: z
|
|
.object({
|
|
success: z.boolean(),
|
|
context: z.literal('organization'),
|
|
userId: z.string(),
|
|
organizationId: z.string(),
|
|
data: organizationBillingDataSchema.nullable(),
|
|
})
|
|
.passthrough(),
|
|
},
|
|
})
|
|
|
|
export type OrganizationRoster = z.infer<typeof organizationRosterSchema>
|
|
export type RosterWorkspaceAccess = z.infer<typeof rosterWorkspaceAccessSchema>
|
|
export type RosterMember = z.infer<typeof rosterMemberSchema>
|
|
export type RosterPendingInvitation = z.infer<typeof rosterPendingInvitationSchema>
|
|
export type OrganizationMembersResponse = z.infer<typeof listOrganizationMembersResponseSchema>
|
|
export type OrganizationMemberUsageLimitData = z.infer<
|
|
typeof organizationMemberUsageLimitDataSchema
|
|
>
|