pull/715/head
Travis Fischer 2025-05-20 16:10:48 +07:00
rodzic 2484e7efdb
commit 264a5455a3
25 zmienionych plików z 258 dodań i 201 usunięć

Wyświetl plik

@ -75,11 +75,11 @@ export function registerV1DeploymentsCreateDeployment(
// TODO: investigate better short hash generation
const hash = sha256().slice(0, 8)
const deploymentId = `${project.id}@${hash}`
const deploymentIdentifier = `${project.identifier}@${hash}`
assert(
validators.deploymentId(deploymentId),
validators.deploymentIdentifier(deploymentIdentifier),
400,
`Invalid deployment id "${deploymentId}"`
`Invalid deployment identifier "${deploymentIdentifier}"`
)
let { version } = body
@ -87,13 +87,13 @@ export function registerV1DeploymentsCreateDeployment(
assert(
version,
400,
`Deployment "version" field is required to publish deployment "${deploymentId}"`
`Deployment "version" field is required to publish deployment "${deploymentIdentifier}"`
)
}
if (version) {
version = normalizeDeploymentVersion({
deploymentId,
deploymentIdentifier,
project,
version
})
@ -102,36 +102,36 @@ export function registerV1DeploymentsCreateDeployment(
// Validate OpenAPI originUrl and originAdapter
await validateDeploymentOriginAdapter({
...pick(body, 'originUrl', 'originAdapter'),
deploymentId,
deploymentIdentifier,
logger
})
let [[deployment]] = await db.transaction(async (tx) => {
return Promise.all([
// Create the deployment
tx
.insert(schema.deployments)
.values({
...body,
id: deploymentId,
hash,
userId: user.id,
teamId: teamMember?.teamId,
projectId,
version
})
.returning(),
// Create the deployment
let [deployment] = await db
.insert(schema.deployments)
.values({
...body,
identifier: deploymentIdentifier,
hash,
userId: user.id,
teamId: teamMember?.teamId,
projectId,
version
})
.returning()
assert(
deployment,
500,
`Failed to create deployment "${deploymentIdentifier}"`
)
// Update the project
tx
.update(schema.projects)
.set({
lastDeploymentId: deploymentId
})
.where(eq(schema.projects.id, projectId))
])
})
assert(deployment, 500, `Failed to create deployment "${deploymentId}"`)
// Update the project
await db
.update(schema.projects)
.set({
lastDeploymentId: deployment.id
})
.where(eq(schema.projects.id, projectId))
if (publish) {
deployment = await publishDeployment(c, {

Wyświetl plik

@ -4,7 +4,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { schema } from '@/db'
import { acl } from '@/lib/acl'
import { tryGetDeployment } from '@/lib/deployments/try-get-deployment'
import { getDeploymentById } from '@/lib/deployments/get-deployment-by-id'
import {
openapiAuthenticatedSecuritySchemas,
openapiErrorResponse404,
@ -45,7 +45,8 @@ export function registerV1DeploymentsGetDeployment(
const { deploymentId } = c.req.valid('param')
const { populate = [] } = c.req.valid('query')
const deployment = await tryGetDeployment(c, deploymentId, {
const deployment = await getDeploymentById({
deploymentId,
with: {
...Object.fromEntries(populate.map((field) => [field, true]))
}

Wyświetl plik

@ -4,8 +4,8 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { schema } from '@/db'
import { acl } from '@/lib/acl'
import { getDeploymentById } from '@/lib/deployments/get-deployment-by-id'
import { publishDeployment } from '@/lib/deployments/publish-deployment'
import { tryGetDeployment } from '@/lib/deployments/try-get-deployment'
import {
openapiAuthenticatedSecuritySchemas,
openapiErrorResponse404,
@ -54,7 +54,7 @@ export function registerV1DeploymentsPublishDeployment(
const { version } = c.req.valid('json')
// First ensure the deployment exists and the user has access to it
const deployment = await tryGetDeployment(c, deploymentId)
const deployment = await getDeploymentById({ deploymentId })
assert(deployment, 404, `Deployment not found "${deploymentId}"`)
await acl(c, deployment, { label: 'Deployment' })

Wyświetl plik

@ -4,7 +4,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { acl } from '@/lib/acl'
import { tryGetDeployment } from '@/lib/deployments/try-get-deployment'
import { getDeploymentById } from '@/lib/deployments/get-deployment-by-id'
import {
openapiAuthenticatedSecuritySchemas,
openapiErrorResponse404,
@ -53,7 +53,7 @@ export function registerV1DeploymentsUpdateDeployment(
const body = c.req.valid('json')
// First ensure the deployment exists and the user has access to it
let deployment = await tryGetDeployment(c, deploymentId)
let deployment = await getDeploymentById({ deploymentId })
assert(deployment, 404, `Deployment not found "${deploymentId}"`)
await acl(c, deployment, { label: 'Deployment' })

Wyświetl plik

@ -53,17 +53,17 @@ export function registerV1ProjectsCreateProject(
const teamMember = c.get('teamMember')
const namespace = teamMember ? teamMember.teamSlug : user.username
const id = `${namespace}/${body.name}`
const identifier = `${namespace}/${body.name}`
const [project] = await db
.insert(schema.projects)
.values({
...body,
id,
identifier,
teamId: teamMember?.teamId,
userId: user.id,
_secret: sha256(),
_providerToken: createProviderToken({ id })
_providerToken: createProviderToken({ identifier })
})
.returning()
assert(project, 500, `Failed to create project "${body.name}"`)

Wyświetl plik

@ -2,8 +2,8 @@ import jwt from 'jsonwebtoken'
import { env } from '@/lib/env'
export function createProviderToken(project: { id: string }) {
export function createProviderToken(project: { identifier: string }) {
// TODO: Possibly in the future store stripe account ID as well and require
// provider tokens to refresh after account changes?
return jwt.sign({ projectId: project.id }, env.JWT_SECRET)
return jwt.sign({ projectIdentifier: project.identifier }, env.JWT_SECRET)
}

Wyświetl plik

@ -1,8 +1,7 @@
import { assert } from '@agentic/platform-core'
import { parseFaasIdentifier } from '@agentic/platform-validators'
import type { AuthenticatedContext } from '@/lib/types'
import { and, db, eq, schema } from '@/db'
import { and, db, eq, type RawDeployment, type RawProject, schema } from '@/db'
import { acl } from '@/lib/acl'
import { upsertStripeConnectCustomer } from '@/lib/billing/upsert-stripe-connect-customer'
import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer'
@ -25,12 +24,42 @@ export async function upsertConsumer(
assert(consumerId || deploymentId, 400, 'Missing required "deploymentId"')
const logger = c.get('logger')
const userId = c.get('userId')
let deployment: RawDeployment | undefined
let project: RawProject | undefined
let projectId: string | undefined
async function initDeploymentAndProject() {
assert(deploymentId, 400, 'Missing required "deploymentId"')
if (deployment && project) {
// Already initialized
return
}
deployment = await db.query.deployments.findFirst({
where: eq(schema.deployments.id, deploymentId),
with: {
project: true
}
})
assert(deployment, 404, `Deployment not found "${deploymentId}"`)
assert(
!deployment.deletedAt,
410,
`Deployment has been deleted by its owner "${deployment.id}"`
)
await acl(c, deployment, { label: 'Deployment' })
project = deployment.project!
assert(
project,
404,
`Project not found "${projectId}" for deployment "${deploymentId}"`
)
await acl(c, project, { label: 'Project' })
}
if (deploymentId) {
const parsedIds = parseFaasIdentifier(deploymentId)
assert(parsedIds, 400, 'Invalid "deploymentId"')
projectId = parsedIds.projectId
await initDeploymentAndProject()
}
if (!consumerId) {
@ -83,28 +112,15 @@ export async function upsertConsumer(
existingConsumer.plan !== plan ||
existingConsumer.deploymentId !== deploymentId,
409,
`User "${user.email}" already has an active subscription to plan "${plan}" for project "${projectId}"`
plan
? `User "${user.email}" already has an active subscription to plan "${plan}" for project "${projectId}"`
: `User "${user.email}" already has cancelled their subscription for project "${projectId}"`
)
const deployment = await db.query.deployments.findFirst({
where: eq(schema.deployments.id, deploymentId),
with: {
project: true
}
})
assert(deployment, 404, `Deployment not found "${deploymentId}"`)
const { project } = deployment
assert(
project,
404,
`Project not found "${projectId}" for deployment "${deploymentId}"`
)
assert(
!deployment.deletedAt,
410,
`Deployment has been deleted by its owner "${deployment.id}"`
)
await initDeploymentAndProject()
assert(deployment, 500, `Error getting deployment "${deploymentId}"`)
assert(project, 500, `Error getting project "${projectId}"`)
if (plan) {
const pricingPlan = deployment.pricingPlans.find((p) => p.slug === plan)

Wyświetl plik

@ -0,0 +1,27 @@
import { db, eq, type RawDeployment, schema } from '@/db'
/**
* Finds the Deployment with the given id.
*
* Does not take care of ACLs.
*
* Returns `undefined` if not found.
*/
export async function getDeploymentById({
deploymentId,
...dbQueryOpts
}: {
deploymentId: string
with?: {
user?: true
team?: true
project?: true
}
}): Promise<RawDeployment | undefined> {
const deployment = await db.query.deployments.findFirst({
...dbQueryOpts,
where: eq(schema.deployments.id, deploymentId)
})
return deployment
}

Wyświetl plik

@ -4,11 +4,11 @@ import semver from 'semver'
import type { RawProject } from '@/db'
export function normalizeDeploymentVersion({
deploymentId,
deploymentIdentifier,
version: rawVersion,
project
}: {
deploymentId: string
deploymentIdentifier: string
version: string
project: RawProject
}): string | undefined {
@ -18,14 +18,14 @@ export function normalizeDeploymentVersion({
assert(
semver.valid(version),
400,
`Invalid semver version "${version}" for deployment "${deploymentId}"`
`Invalid semver version "${version}" for deployment "${deploymentIdentifier}"`
)
const lastPublishedVersion = project.lastPublishedDeployment?.version
assert(
!lastPublishedVersion || semver.gt(version, lastPublishedVersion),
400,
`Semver version "${version}" must be greater than the current published version "${lastPublishedVersion}" for deployment "${deploymentId}"`
`Semver version "${version}" must be greater than the current published version "${lastPublishedVersion}" for deployment "${deploymentIdentifier}"`
)
return version

Wyświetl plik

@ -26,7 +26,7 @@ export async function publishDeployment(
await acl(ctx, project, { label: 'Project' })
const version = normalizeDeploymentVersion({
deploymentId: deployment.id,
deploymentIdentifier: deployment.identifier,
project,
version: rawVersion
})

Wyświetl plik

@ -6,46 +6,57 @@ import { db, eq, type RawDeployment, schema } from '@/db'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
/**
* Attempts to find the Deployment matching the given identifier.
* Attempts to find the Deployment matching the given deployment identifier.
*
* Throws a HTTP 404 error if not found.
*
* Does not take care of ACLs.
*/
export async function tryGetDeployment(
export async function tryGetDeploymentByIdentifier(
ctx: AuthenticatedContext,
identifier: string,
dbQueryOpts: {
{
deploymentIdentifier,
...dbQueryOpts
}: {
deploymentIdentifier: string
with?: {
user?: true
team?: true
project?: true
}
} = {}
): Promise<RawDeployment | undefined> {
}
): Promise<RawDeployment> {
const user = await ensureAuthUser(ctx)
const teamMember = ctx.get('teamMember')
const namespace = teamMember ? teamMember.teamSlug : user.username
const parsedFaas = parseFaasIdentifier(identifier, {
const parsedFaas = parseFaasIdentifier(deploymentIdentifier, {
namespace
})
assert(parsedFaas, 400, `Invalid deployment identifier "${identifier}"`)
assert(
parsedFaas,
400,
`Invalid deployment identifier "${deploymentIdentifier}"`
)
const { projectId, deploymentHash, version } = parsedFaas
const { projectIdentifier, deploymentHash, version } = parsedFaas
if (deploymentHash) {
const deploymentId = `${projectId}@${deploymentHash}`
const deploymentIdentifier = `${projectIdentifier}@${deploymentHash}`
const deployment = await db.query.deployments.findFirst({
...dbQueryOpts,
where: eq(schema.deployments.id, deploymentId)
where: eq(schema.deployments.identifier, deploymentIdentifier)
})
assert(deployment, 404, `Deployment not found "${deploymentId}"`)
assert(deployment, 404, `Deployment not found "${deploymentIdentifier}"`)
return deployment
} else if (version === 'latest') {
const project = await db.query.projects.findFirst({
...dbQueryOpts,
where: eq(schema.projects.id, projectId)
where: eq(schema.projects.identifier, projectIdentifier)
})
assert(project, 404, `Project not found "${projectId}"`)
assert(project, 404, `Project not found "${projectIdentifier}"`)
assert(
project.lastPublishedDeploymentId,
404,
@ -66,9 +77,9 @@ export async function tryGetDeployment(
} else if (version === 'dev') {
const project = await db.query.projects.findFirst({
...dbQueryOpts,
where: eq(schema.projects.id, projectId)
where: eq(schema.projects.id, projectIdentifier)
})
assert(project, 404, `Project not found "${projectId}"`)
assert(project, 404, `Project not found "${projectIdentifier}"`)
assert(
project.lastDeploymentId,
404,
@ -88,5 +99,5 @@ export async function tryGetDeployment(
return deployment
}
assert(false, 400, `Invalid Deployment identifier "${identifier}"`)
assert(false, 400, `Invalid Deployment identifier "${deploymentIdentifier}"`)
}

Wyświetl plik

@ -10,12 +10,12 @@ import { validateOpenAPISpec } from '@/lib/validate-openapi-spec'
* NOTE: This method may mutate `originAdapter.spec`.
*/
export async function validateDeploymentOriginAdapter({
deploymentId,
deploymentIdentifier,
originUrl,
originAdapter,
logger
}: {
deploymentId: string
deploymentIdentifier: string
originUrl: string
originAdapter: DeploymentOriginAdapter
logger: Logger
@ -23,14 +23,14 @@ export async function validateDeploymentOriginAdapter({
assert(
originUrl,
400,
`Origin URL is required for deployment "${deploymentId}"`
`Origin URL is required for deployment "${deploymentIdentifier}"`
)
if (originAdapter.type === 'openapi') {
assert(
originAdapter.spec,
400,
`OpenAPI spec is required for deployment "${deploymentId}" with origin adapter type set to "openapi"`
`OpenAPI spec is required for deployment "${deploymentIdentifier}" with origin adapter type set to "openapi"`
)
// Validate and normalize the OpenAPI spec
@ -54,7 +54,7 @@ export async function validateDeploymentOriginAdapter({
assert(
originAdapter.type === 'raw',
400,
`Invalid origin adapter type "${originAdapter.type}" for deployment "${deploymentId}"`
`Invalid origin adapter type "${originAdapter.type}" for deployment "${deploymentIdentifier}"`
)
}
}

Wyświetl plik

@ -5,6 +5,10 @@ import type { AuthenticatedEnv } from '@/lib/types'
import { and, db, eq, schema } from '@/db'
import { aclTeamMember } from '@/lib/acl-team-member'
// TODO: Instead of accepting `teamId` query param, change the authenticate
// middleware to accept a different JWT payload and then use that to
// determine the intended user and/or team.
export const team = createMiddleware<AuthenticatedEnv>(
async function teamMiddleware(ctx, next) {
const teamId = ctx.req.query('teamId')

Wyświetl plik

@ -33,7 +33,10 @@ export function stripeId<U extends string, T extends Readonly<[U, ...U[]]>>(
/**
* `namespace/projectName`
*/
export function projectId<U extends string, T extends Readonly<[U, ...U[]]>>(
export function projectIdentifier<
U extends string,
T extends Readonly<[U, ...U[]]>
>(
config?: PgVarcharConfig<T | Writable<T>, never>
): PgVarcharBuilderInitial<'', Writable<T>, 130> {
return varchar({ length: 130, ...config })
@ -42,7 +45,10 @@ export function projectId<U extends string, T extends Readonly<[U, ...U[]]>>(
/**
* `namespace/projectName@hash`
*/
export function deploymentId<U extends string, T extends Readonly<[U, ...U[]]>>(
export function deploymentIdentifier<
U extends string,
T extends Readonly<[U, ...U[]]>
>(
config?: PgVarcharConfig<T | Writable<T>, never>
): PgVarcharBuilderInitial<'', Writable<T>, 160> {
return varchar({ length: 160, ...config })

Wyświetl plik

@ -1,4 +1,3 @@
import { validators } from '@agentic/platform-validators'
import { relations } from '@fisch0920/drizzle-orm'
import {
boolean,
@ -14,9 +13,7 @@ import {
createSelectSchema,
createUpdateSchema,
cuid,
deploymentId,
id,
projectId,
stripeId,
timestamps
} from './common'
@ -70,7 +67,7 @@ export const consumers = pgTable(
.references(() => users.id),
// The project this user is subscribed to
projectId: projectId()
projectId: cuid()
.notNull()
.references(() => projects.id, {
onDelete: 'cascade'
@ -78,7 +75,7 @@ export const consumers = pgTable(
// The specific deployment this user is subscribed to, since pricing can
// change across deployment versions)
deploymentId: deploymentId()
deploymentId: cuid()
.notNull()
.references(() => deployments.id, {
onDelete: 'cascade'
@ -134,17 +131,7 @@ export const consumersRelations = relations(consumers, ({ one }) => ({
}))
export const consumerSelectSchema = createSelectSchema(consumers, {
_stripeSubscriptionItemIdMap: stripeSubscriptionItemIdMapSchema,
deploymentId: (schema) =>
schema.refine((id) => validators.deploymentId(id), {
message: 'Invalid deployment id'
}),
projectId: (schema) =>
schema.refine((id) => validators.projectId(id), {
message: 'Invalid project id'
})
_stripeSubscriptionItemIdMap: stripeSubscriptionItemIdMapSchema
})
.omit({
_stripeSubscriptionId: true,
@ -171,10 +158,7 @@ export const consumerSelectSchema = createSelectSchema(consumers, {
.openapi('Consumer')
export const consumerInsertSchema = createInsertSchema(consumers, {
deploymentId: (schema) =>
schema.refine((id) => validators.deploymentId(id), {
message: 'Invalid deployment id'
}),
deploymentId: (schema) => schema.cuid2().optional(),
plan: z.string().nonempty()
})
@ -186,12 +170,7 @@ export const consumerInsertSchema = createInsertSchema(consumers, {
.strict()
export const consumerUpdateSchema = createUpdateSchema(consumers, {
deploymentId: (schema) =>
schema
.refine((id) => validators.deploymentId(id), {
message: 'Invalid deployment id'
})
.optional()
deploymentId: (schema) => schema.cuid2().optional()
})
.pick({
plan: true,

Wyświetl plik

@ -5,17 +5,19 @@ import {
index,
jsonb,
pgTable,
text
text,
uniqueIndex
} from '@fisch0920/drizzle-orm/pg-core'
import { z } from '@hono/zod-openapi'
import { deploymentIdentifierSchema, projectIdSchema } from '../schemas'
import {
createInsertSchema,
createSelectSchema,
createUpdateSchema,
cuid,
deploymentId,
projectId,
deploymentIdentifier,
id,
timestamps
} from './common'
import { projects } from './project'
@ -31,10 +33,10 @@ import { users } from './user'
export const deployments = pgTable(
'deployments',
{
// namespace/projectName@hash
id: deploymentId().primaryKey(),
id,
...timestamps,
identifier: deploymentIdentifier().unique().notNull(),
hash: text().notNull(),
version: text(),
@ -48,7 +50,7 @@ export const deployments = pgTable(
.notNull()
.references(() => users.id),
teamId: cuid().references(() => teams.id),
projectId: projectId()
projectId: cuid()
.notNull()
.references(() => projects.id, {
onDelete: 'cascade'
@ -78,6 +80,7 @@ export const deployments = pgTable(
// coupons: jsonb().$type<Coupon[]>().default([]).notNull()
},
(table) => [
uniqueIndex('deployment_identifier_idx').on(table.identifier),
index('deployment_userId_idx').on(table.userId),
index('deployment_teamId_idx').on(table.teamId),
index('deployment_projectId_idx').on(table.projectId),
@ -112,12 +115,7 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({
// TODO: virtual openapi spec? (hide openapi.servers)
export const deploymentSelectSchema = createSelectSchema(deployments, {
// build: z.object({}),
// env: z.object({}),
id: (schema) =>
schema.refine((id) => validators.deploymentId(id), {
message: 'Invalid deployment id'
}),
identifier: deploymentIdentifierSchema,
hash: (schema) =>
schema.refine((hash) => validators.deploymentHash(hash), {
@ -148,10 +146,7 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
.openapi('Deployment')
export const deploymentInsertSchema = createInsertSchema(deployments, {
projectId: (schema) =>
schema.refine((id) => validators.projectId(id), {
message: 'Invalid project id'
}),
projectId: projectIdSchema,
iconUrl: (schema) =>
schema

Wyświetl plik

@ -5,11 +5,9 @@ import {
createInsertSchema,
createSelectSchema,
cuid,
deploymentId,
id,
logEntryLevelEnum,
logEntryTypeEnum,
projectId,
timestamps
} from './common'
import { consumers } from './consumer'
@ -39,8 +37,8 @@ export const logEntries = pgTable(
// relations (optional)
userId: cuid(),
projectId: projectId(),
deploymentId: deploymentId(),
projectId: cuid(),
deploymentId: cuid(),
consumerId: cuid(),
// misc metadata (optional)

Wyświetl plik

@ -6,19 +6,21 @@ import {
integer,
jsonb,
pgTable,
text
text,
uniqueIndex
} from '@fisch0920/drizzle-orm/pg-core'
import { z } from '@hono/zod-openapi'
import { projectIdentifierSchema } from '../schemas'
import {
createInsertSchema,
createSelectSchema,
createUpdateSchema,
cuid,
deploymentId,
id,
pricingCurrencyEnum,
pricingIntervalEnum,
projectId,
projectIdentifier,
stripeId,
timestamps
} from './common'
@ -38,10 +40,10 @@ import { users } from './user'
export const projects = pgTable(
'projects',
{
// namespace/projectName
id: projectId().primaryKey(),
id,
...timestamps,
identifier: projectIdentifier().unique().notNull(),
name: text().notNull(),
alias: text(),
@ -51,10 +53,10 @@ export const projects = pgTable(
teamId: cuid(),
// Most recently published Deployment if one exists
lastPublishedDeploymentId: deploymentId(),
lastPublishedDeploymentId: cuid(),
// Most recent Deployment if one exists
lastDeploymentId: deploymentId(),
lastDeploymentId: cuid(),
applicationFeePercent: integer().default(20).notNull(),
@ -122,6 +124,7 @@ export const projects = pgTable(
_stripeAccountId: stripeId()
},
(table) => [
uniqueIndex('project_identifier_idx').on(table.identifier),
index('project_userId_idx').on(table.userId),
index('project_teamId_idx').on(table.teamId),
index('project_alias_idx').on(table.alias),
@ -159,6 +162,8 @@ export const projectsRelations = relations(projects, ({ one }) => ({
}))
export const projectSelectSchema = createSelectSchema(projects, {
identifier: projectIdentifierSchema,
applicationFeePercent: (schema) => schema.nonnegative(),
_stripeProductIdMap: stripeProductIdMapSchema,
@ -202,10 +207,7 @@ export const projectSelectSchema = createSelectSchema(projects, {
.openapi('Project')
export const projectInsertSchema = createInsertSchema(projects, {
id: (schema) =>
schema.refine((id) => validators.projectId(id), {
message: 'Invalid project id'
}),
identifier: projectIdentifierSchema,
name: (schema) =>
schema.refine((name) => validators.projectName(name), {

Wyświetl plik

@ -13,19 +13,25 @@ function getCuidSchema(idLabel: string) {
export const cuidSchema = getCuidSchema('id')
export const userIdSchema = getCuidSchema('user id')
export const teamIdSchema = getCuidSchema('team id')
export const consumerIdSchema = getCuidSchema('consumer id')
export const projectIdSchema = getCuidSchema('project id')
export const deploymentIdSchema = getCuidSchema('deployment id')
export const logEntryIdSchema = getCuidSchema('log entry id')
export const projectIdSchema = z
export const projectIdentifierSchema = z
.string()
.refine((id) => validators.projectId(id), {
message: 'Invalid project id'
.refine((id) => validators.projectIdentifier(id), {
message: 'Invalid project identifier'
})
.openapi('ProjectIdentifier')
export const deploymentIdSchema = z
export const deploymentIdentifierSchema = z
.string()
.refine((id) => validators.deploymentId(id), {
message: 'Invalid deployment id'
.refine((id) => validators.deploymentIdentifier(id), {
message: 'Invalid deployment identifier'
})
.openapi('DeploymentIdentifier')
export const usernameSchema = z
.string()

Wyświetl plik

@ -6,14 +6,16 @@ import * as validators from './validators'
function success(...args: Parameters<typeof parseFaasIdentifier>) {
const result = parseFaasIdentifier(...args)
expect(result).toBeTruthy()
expect(result!.projectId).toBeTruthy()
expect(result!.projectIdentifier).toBeTruthy()
expect(result!.version || result!.deploymentHash).toBeTruthy()
expect(validators.projectId(result!.projectId)).toBe(true)
expect(validators.projectIdentifier(result!.projectIdentifier)).toBe(true)
expect(validators.servicePath(result!.servicePath)).toBe(true)
if (result!.deploymentHash) {
expect(validators.deploymentHash(result!.deploymentHash)).toBe(true)
expect(validators.deploymentId(result!.deploymentId!)).toBe(true)
expect(validators.deploymentIdentifier(result!.deploymentIdentifier!)).toBe(
true
)
}
expect(result).toMatchSnapshot()

Wyświetl plik

@ -5,7 +5,7 @@ import { parseFaasUri } from './parse-faas-uri'
function success(value: string) {
const result = parseFaasUri(value)
expect(result).toBeTruthy()
expect(result?.projectId).toBeTruthy()
expect(result?.projectIdentifier).toBeTruthy()
expect(result?.version || result?.deploymentHash).toBeTruthy()
expect(result).toMatchSnapshot()
}

Wyświetl plik

@ -22,15 +22,15 @@ export function parseFaasUri(uri: string): ParsedFaasIdentifier | undefined {
const pdsMatch = uri.match(projectDeploymentServiceRe)
if (pdsMatch) {
const projectId = pdsMatch[1]!
const projectIdentifier = pdsMatch[1]!
const deploymentHash = pdsMatch[2]!
const servicePath = pdsMatch[3] || '/'
return {
projectId,
projectIdentifier,
deploymentHash,
servicePath,
deploymentId: `${projectId}@${deploymentHash}`
deploymentIdentifier: `${projectIdentifier}@${deploymentHash}`
}
}
@ -38,7 +38,7 @@ export function parseFaasUri(uri: string): ParsedFaasIdentifier | undefined {
if (pvsMatch) {
return {
projectId: pvsMatch[1]!,
projectIdentifier: pvsMatch[1]!,
version: pvsMatch[2]!,
servicePath: pvsMatch[3] || '/'
}
@ -48,7 +48,7 @@ export function parseFaasUri(uri: string): ParsedFaasIdentifier | undefined {
if (psMatch) {
return {
projectId: psMatch[1]!,
projectIdentifier: psMatch[1]!,
servicePath: psMatch[2] || '/',
version: 'latest'
}

Wyświetl plik

@ -1,13 +1,13 @@
export type ParsedFaasIdentifier = {
projectId: string
projectIdentifier: string
servicePath: string
deploymentHash?: string
deploymentId?: string
deploymentIdentifier?: string
version?: string
} & (
| {
deploymentHash: string
deploymentId: string
deploymentIdentifier: string
}
| {
version: string

Wyświetl plik

@ -72,40 +72,50 @@ test('deploymentHash failure', () => {
expect(validators.deploymentHash('012345678')).toBe(false)
})
test('projectId success', () => {
expect(validators.projectId('username/project-name')).toBe(true)
expect(validators.projectId('a/123')).toBe(true)
test('projectIdentifier success', () => {
expect(validators.projectIdentifier('username/project-name')).toBe(true)
expect(validators.projectIdentifier('a/123')).toBe(true)
})
test('projectId failure', () => {
expect(validators.projectId('aaa//0123')).toBe(false)
expect(validators.projectId('foo@bar')).toBe(false)
expect(validators.projectId('abc/1.23')).toBe(false)
expect(validators.projectId('012345678/123@latest')).toBe(false)
expect(validators.projectId('foo@dev')).toBe(false)
expect(validators.projectId('username/Project-Name')).toBe(false)
expect(validators.projectId('_/___')).toBe(false)
test('projectIdentifier failure', () => {
expect(validators.projectIdentifier('aaa//0123')).toBe(false)
expect(validators.projectIdentifier('foo@bar')).toBe(false)
expect(validators.projectIdentifier('abc/1.23')).toBe(false)
expect(validators.projectIdentifier('012345678/123@latest')).toBe(false)
expect(validators.projectIdentifier('foo@dev')).toBe(false)
expect(validators.projectIdentifier('username/Project-Name')).toBe(false)
expect(validators.projectIdentifier('_/___')).toBe(false)
})
test('deploymentId success', () => {
expect(validators.deploymentId('username/project-name@01234567')).toBe(true)
expect(validators.deploymentId('a/123@01234567')).toBe(true)
test('deploymentIdentifier success', () => {
expect(
validators.deploymentIdentifier('username/project-name@01234567')
).toBe(true)
expect(validators.deploymentIdentifier('a/123@01234567')).toBe(true)
})
test('deploymentId failure', () => {
expect(validators.deploymentId('username/project-name@012345678')).toBe(false)
expect(validators.deploymentId('username/project-name@latest')).toBe(false)
expect(validators.deploymentId('username/project-name@dev')).toBe(false)
expect(validators.deploymentId('username/Project-Name@01234567')).toBe(false)
expect(validators.deploymentId('a/123@0123A567')).toBe(false)
expect(validators.deploymentId('_/___@012.4567')).toBe(false)
expect(validators.deploymentId('_/___@01234567')).toBe(false)
expect(validators.deploymentId('aaa//0123@01234567')).toBe(false)
expect(validators.deploymentId('foo@bar@01234567')).toBe(false)
expect(validators.deploymentId('abc/1.23@01234567')).toBe(false)
expect(validators.deploymentId('012345678/123@latest')).toBe(false)
expect(validators.deploymentId('012345678/123@dev')).toBe(false)
expect(validators.deploymentId('012345678/123@1.0.1')).toBe(false)
test('deploymentIdentifier failure', () => {
expect(
validators.deploymentIdentifier('username/project-name@012345678')
).toBe(false)
expect(validators.deploymentIdentifier('username/project-name@latest')).toBe(
false
)
expect(validators.deploymentIdentifier('username/project-name@dev')).toBe(
false
)
expect(
validators.deploymentIdentifier('username/Project-Name@01234567')
).toBe(false)
expect(validators.deploymentIdentifier('a/123@0123A567')).toBe(false)
expect(validators.deploymentIdentifier('_/___@012.4567')).toBe(false)
expect(validators.deploymentIdentifier('_/___@01234567')).toBe(false)
expect(validators.deploymentIdentifier('aaa//0123@01234567')).toBe(false)
expect(validators.deploymentIdentifier('foo@bar@01234567')).toBe(false)
expect(validators.deploymentIdentifier('abc/1.23@01234567')).toBe(false)
expect(validators.deploymentIdentifier('012345678/123@latest')).toBe(false)
expect(validators.deploymentIdentifier('012345678/123@dev')).toBe(false)
expect(validators.deploymentIdentifier('012345678/123@1.0.1')).toBe(false)
})
test('serviceName success', () => {

Wyświetl plik

@ -40,11 +40,11 @@ export function deploymentHash(value: string): boolean {
return !!value && deploymentHashRe.test(value)
}
export function projectId(value: string): boolean {
export function projectIdentifier(value: string): boolean {
return !!value && projectRe.test(value)
}
export function deploymentId(value: string): boolean {
export function deploymentIdentifier(value: string): boolean {
return !!value && deploymentRe.test(value)
}