diff --git a/apps/api/src/api-v1/index.ts b/apps/api/src/api-v1/index.ts index 17d0d62a..be60021c 100644 --- a/apps/api/src/api-v1/index.ts +++ b/apps/api/src/api-v1/index.ts @@ -7,6 +7,7 @@ import { registerHealthCheck } from './health-check' import { registerV1ProjectsCreateProject } from './projects/create-project' import { registerV1ProjectsGetProject } from './projects/get-project' import { registerV1ProjectsListProjects } from './projects/list-projects' +import { registerV1ProjectsUpdateProject } from './projects/update-project' import { registerV1TeamsCreateTeam } from './teams/create-team' import { registerV1TeamsDeleteTeam } from './teams/delete-team' import { registerV1TeamsGetTeam } from './teams/get-team' @@ -46,8 +47,9 @@ registerV1TeamsMembersDeleteTeamMember(pri) // Projects crud registerV1ProjectsCreateProject(pri) -registerV1ProjectsGetProject(pri) registerV1ProjectsListProjects(pri) +registerV1ProjectsGetProject(pri) +registerV1ProjectsUpdateProject(pri) // TODO // pub.get('/projects/alias/:alias(.+)', require('./projects').readByAlias) @@ -59,7 +61,6 @@ registerV1ProjectsListProjects(pri) // middleware.authenticate({ passthrough: true }), // require('./projects').read // ) -// pri.put('/projects/:project(.+)', require('./projects').update) // Setup routes and middleware apiV1.route('/', pub) diff --git a/apps/api/src/api-v1/projects/create-project.ts b/apps/api/src/api-v1/projects/create-project.ts index a5f2d401..68c4b781 100644 --- a/apps/api/src/api-v1/projects/create-project.ts +++ b/apps/api/src/api-v1/projects/create-project.ts @@ -3,7 +3,8 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, schema } from '@/db' import { aclTeamMember } from '@/lib/acl-team-member' -import { assert, parseZodSchema } from '@/lib/utils' +import { getProviderToken } from '@/lib/auth/get-provider-token' +import { assert, parseZodSchema, sha256 } from '@/lib/utils' const route = createRoute({ description: 'Creates a new project.', @@ -57,7 +58,9 @@ export function registerV1ProjectsCreateProject( ...body, teamId: teamMember?.teamId, userId: user.id, - id + id, + _secret: sha256(), + _providerToken: getProviderToken({ id }) }) .returning() assert(project, 404, `Failed to create project "${body.name}"`) diff --git a/apps/api/src/api-v1/projects/update-project.ts b/apps/api/src/api-v1/projects/update-project.ts new file mode 100644 index 00000000..df987fc3 --- /dev/null +++ b/apps/api/src/api-v1/projects/update-project.ts @@ -0,0 +1,57 @@ +import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' + +import type { AuthenticatedEnv } from '@/lib/types' +import { db, eq, schema } from '@/db' +import { assert, parseZodSchema } from '@/lib/utils' + +import { projectIdParamsSchema } from './schemas' + +const route = createRoute({ + description: 'Updates a project.', + tags: ['projects'], + operationId: 'updateProject', + method: 'put', + path: 'projects/{projectId}', + security: [{ bearerAuth: [] }], + request: { + params: projectIdParamsSchema, + body: { + required: true, + content: { + 'application/json': { + schema: schema.projectUpdateSchema + } + } + } + }, + responses: { + 200: { + description: 'The updated project', + content: { + 'application/json': { + schema: schema.projectSelectSchema + } + } + } + // TODO + // ...openApiErrorResponses + } +}) + +export function registerV1ProjectsUpdateProject( + app: OpenAPIHono +) { + return app.openapi(route, async (c) => { + const { projectId } = c.req.valid('param') + const body = c.req.valid('json') + + const [project] = await db + .update(schema.projects) + .set(body) + .where(eq(schema.projects.id, projectId)) + .returning() + assert(project, 404, `Failed to update project "${projectId}"`) + + return c.json(parseZodSchema(schema.projectSelectSchema, project)) + }) +} diff --git a/apps/api/src/db/schema/project.ts b/apps/api/src/db/schema/project.ts index 4b07cc26..ab14eaea 100644 --- a/apps/api/src/db/schema/project.ts +++ b/apps/api/src/db/schema/project.ts @@ -52,10 +52,10 @@ export const projects = pgTable( isStripeConnectEnabled: boolean().default(false).notNull(), // All deployments share the same underlying proxy secret - _secret: text(), + _secret: text().notNull(), // Auth token used to access the saasify API on behalf of this project - // _providerToken: text().notNull(), + _providerToken: text().notNull(), // TODO: Full-text search _text: text().default('').notNull(), @@ -101,6 +101,7 @@ export const projects = pgTable( (table) => [ index('project_userId_idx').on(table.userId), index('project_teamId_idx').on(table.teamId), + index('project_alias_idx').on(table.alias), index('project_createdAt_idx').on(table.createdAt), index('project_updatedAt_idx').on(table.updatedAt) ] @@ -162,7 +163,7 @@ export const projectSelectSchema = createSelectSchema(projects, { }) .omit({ _secret: true, - // _providerToken: true, + _providerToken: true, _text: true, _webhooks: true, _stripeCouponIds: true, @@ -208,15 +209,13 @@ export const projectInsertSchema = createInsertSchema(projects, { teamId: true }) .strict() -// .refine((data) => { -// return { -// ...data, -// _providerToken: getProviderToken(data) -// } -// }) -// TODO: narrow update schema -export const projectUpdateSchema = createUpdateSchema(projects).strict() +export const projectUpdateSchema = createUpdateSchema(projects) + .pick({ + name: true, + alias: true + }) + .strict() export const projectDebugSelectSchema = createSelectSchema(projects).pick({ id: true, @@ -233,4 +232,3 @@ export const projectDebugSelectSchema = createSelectSchema(projects).pick({ // TODO: virtual saasUrl // TODO: virtual aliasUrl -// TODO: virtual stripeConnectParams