mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-14 06:47:00 +10:00
Return updated resume object from PUT /resume/{id} instead of void (#2688)
* implement PUT response * Refactor resume router and service to use DTOs for input and output schemas * define explicitly, the fields to be returned --------- Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ddbb71fb78
commit
5e8e1349bc
+5
-4
@@ -31,9 +31,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.37",
|
||||
"@ai-sdk/google": "^3.0.21",
|
||||
"@ai-sdk/openai": "^3.0.25",
|
||||
"@ai-sdk/anthropic": "^3.0.38",
|
||||
"@ai-sdk/google": "^3.0.22",
|
||||
"@ai-sdk/openai": "^3.0.26",
|
||||
"@aws-sdk/client-s3": "^3.985.0",
|
||||
"@better-auth/core": "1.5.0-beta.11",
|
||||
"@better-auth/passkey": "1.5.0-beta.11",
|
||||
@@ -66,7 +66,7 @@
|
||||
"@tiptap/pm": "^3.19.0",
|
||||
"@tiptap/react": "^3.19.0",
|
||||
"@tiptap/starter-kit": "^3.19.0",
|
||||
"ai": "^6.0.74",
|
||||
"ai": "^6.0.75",
|
||||
"ai-sdk-ollama": "^3.4.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-auth": "1.5.0-beta.11",
|
||||
@@ -75,6 +75,7 @@
|
||||
"cmdk": "^1.1.1",
|
||||
"dompurify": "^3.3.1",
|
||||
"drizzle-orm": "^1.0.0-beta.15-859cf75",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fuse.js": "^7.1.0",
|
||||
|
||||
Generated
+72
-37
@@ -12,14 +12,14 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@ai-sdk/anthropic':
|
||||
specifier: ^3.0.37
|
||||
version: 3.0.37(zod@4.3.6)
|
||||
specifier: ^3.0.38
|
||||
version: 3.0.38(zod@4.3.6)
|
||||
'@ai-sdk/google':
|
||||
specifier: ^3.0.21
|
||||
version: 3.0.21(zod@4.3.6)
|
||||
specifier: ^3.0.22
|
||||
version: 3.0.22(zod@4.3.6)
|
||||
'@ai-sdk/openai':
|
||||
specifier: ^3.0.25
|
||||
version: 3.0.25(zod@4.3.6)
|
||||
specifier: ^3.0.26
|
||||
version: 3.0.26(zod@4.3.6)
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.985.0
|
||||
version: 3.985.0
|
||||
@@ -117,11 +117,11 @@ importers:
|
||||
specifier: ^3.19.0
|
||||
version: 3.19.0
|
||||
ai:
|
||||
specifier: ^6.0.74
|
||||
version: 6.0.74(zod@4.3.6)
|
||||
specifier: ^6.0.75
|
||||
version: 6.0.75(zod@4.3.6)
|
||||
ai-sdk-ollama:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0(ai@6.0.74(zod@4.3.6))(zod@4.3.6)
|
||||
version: 3.4.0(ai@6.0.75(zod@4.3.6))(zod@4.3.6)
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
@@ -143,6 +143,9 @@ importers:
|
||||
drizzle-orm:
|
||||
specifier: ^1.0.0-beta.15-859cf75
|
||||
version: 1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))
|
||||
drizzle-zod:
|
||||
specifier: ^0.8.3
|
||||
version: 0.8.3(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(zod@4.3.6)
|
||||
es-toolkit:
|
||||
specifier: ^1.44.0
|
||||
version: 1.44.0
|
||||
@@ -327,26 +330,26 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@ai-sdk/anthropic@3.0.37':
|
||||
resolution: {integrity: sha512-tEgcJPw+a6obbF+SHrEiZsx3DNxOHqeY8bK4IpiNsZ8YPZD141R34g3lEAaQnmNN5mGsEJ8SXoEDabuzi8wFJQ==}
|
||||
'@ai-sdk/anthropic@3.0.38':
|
||||
resolution: {integrity: sha512-9MchyPRPni0WzrFeIGNevZpQVfWxaS+MQFupIXYQo9VgHnuO1Vyrp9SBmjkkuoAdBs7GomsWqLZCcNMJAVbdFA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/gateway@3.0.36':
|
||||
resolution: {integrity: sha512-2r1Q6azvqMYxQ1hqfWZmWg4+8MajoldD/ty65XdhCaCoBfvDu7trcvxXDfTSU+3/wZ1JIDky46SWYFOHnTbsBw==}
|
||||
'@ai-sdk/gateway@3.0.37':
|
||||
resolution: {integrity: sha512-w7rxPd2WOuSr4he2ALvtCAM/W1/33rGdv3kkU52tCGj/bJSYJnJDpnTvoWRLNESp9HEkkiNFfBLFZY0PB9n4aQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/google@3.0.21':
|
||||
resolution: {integrity: sha512-qQuvcbDqDPZojtoT45UFCQVH2w3m6KJKKjqJduUsvhN5ZqOXste0h4HgHK8hwGuDfv96Jr9QQEpspbgp6iu5Uw==}
|
||||
'@ai-sdk/google@3.0.22':
|
||||
resolution: {integrity: sha512-g1N5P/jfTiH4qwdv4WT3hkKzzAbITFz457NomtBfjP8Q3SCzdbU9oPK5ACBMG8RN5mc2QPL6DLtM3Hf5T8KPmw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/openai@3.0.25':
|
||||
resolution: {integrity: sha512-DsaN46R98+D1W3lU3fKuPU3ofacboLaHlkAwxJPgJ8eup1AJHmPK1N1y10eJJbJcF6iby8Tf/vanoZxc9JPUfw==}
|
||||
'@ai-sdk/openai@3.0.26':
|
||||
resolution: {integrity: sha512-W/hiwxIfG29IO0Fob1HwWpFssMsNrxWoX8A7DwNGOtKArDBmJNuGzQeU/k0Fnh8WyvZEnfxkjO4oXkSXfVBayg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
@@ -357,10 +360,20 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.14':
|
||||
resolution: {integrity: sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/provider@3.0.7':
|
||||
resolution: {integrity: sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@ai-sdk/provider@3.0.8':
|
||||
resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@antfu/ni@25.0.0':
|
||||
resolution: {integrity: sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==}
|
||||
hasBin: true
|
||||
@@ -4323,8 +4336,8 @@ packages:
|
||||
peerDependencies:
|
||||
ai: ^6.0.70
|
||||
|
||||
ai@6.0.74:
|
||||
resolution: {integrity: sha512-psGKcE4J0WEXZEvZrc6yc4BzGZW4sfMrbXZgYxyL6Mj8eES5oCaoSGEQfYeNdZvj10EEEXr9rsnUAWZdHiQ/DQ==}
|
||||
ai@6.0.75:
|
||||
resolution: {integrity: sha512-pT1xtwaE5Un261GO9Mt9DZ2rv26v+gQZNRXo433CNAMo0JDYs2ef+5MvcNkBe1jXCY9XrXxUG/VT6Y6/9S2SWg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
@@ -5142,6 +5155,12 @@ packages:
|
||||
sqlite3:
|
||||
optional: true
|
||||
|
||||
drizzle-zod@0.8.3:
|
||||
resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==}
|
||||
peerDependencies:
|
||||
drizzle-orm: '>=0.36.0'
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -8190,29 +8209,29 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ai-sdk/anthropic@3.0.37(zod@4.3.6)':
|
||||
'@ai-sdk/anthropic@3.0.38(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.7
|
||||
'@ai-sdk/provider-utils': 4.0.13(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/gateway@3.0.36(zod@4.3.6)':
|
||||
'@ai-sdk/gateway@3.0.37(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.7
|
||||
'@ai-sdk/provider-utils': 4.0.13(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
'@vercel/oidc': 3.1.0
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/google@3.0.21(zod@4.3.6)':
|
||||
'@ai-sdk/google@3.0.22(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.7
|
||||
'@ai-sdk/provider-utils': 4.0.13(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/openai@3.0.25(zod@4.3.6)':
|
||||
'@ai-sdk/openai@3.0.26(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.7
|
||||
'@ai-sdk/provider-utils': 4.0.13(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.13(zod@4.3.6)':
|
||||
@@ -8222,10 +8241,21 @@ snapshots:
|
||||
eventsource-parser: 3.0.6
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.14(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@standard-schema/spec': 1.1.0
|
||||
eventsource-parser: 3.0.6
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/provider@3.0.7':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@ai-sdk/provider@3.0.8':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@antfu/ni@25.0.0':
|
||||
dependencies:
|
||||
ansis: 4.2.0
|
||||
@@ -12839,20 +12869,20 @@ snapshots:
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ai-sdk-ollama@3.4.0(ai@6.0.74(zod@4.3.6))(zod@4.3.6):
|
||||
ai-sdk-ollama@3.4.0(ai@6.0.75(zod@4.3.6))(zod@4.3.6):
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.7
|
||||
'@ai-sdk/provider-utils': 4.0.13(zod@4.3.6)
|
||||
ai: 6.0.74(zod@4.3.6)
|
||||
ai: 6.0.75(zod@4.3.6)
|
||||
ollama: 0.6.3
|
||||
transitivePeerDependencies:
|
||||
- zod
|
||||
|
||||
ai@6.0.74(zod@4.3.6):
|
||||
ai@6.0.75(zod@4.3.6):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 3.0.36(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.7
|
||||
'@ai-sdk/provider-utils': 4.0.13(zod@4.3.6)
|
||||
'@ai-sdk/gateway': 3.0.37(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 4.3.6
|
||||
|
||||
@@ -13553,6 +13583,11 @@ snapshots:
|
||||
postgres: 3.4.7
|
||||
prisma: 7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
|
||||
drizzle-zod@0.8.3(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(zod@4.3.6):
|
||||
dependencies:
|
||||
drizzle-orm: 1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))
|
||||
zod: 4.3.6
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createSelectSchema } from "drizzle-zod";
|
||||
import z from "zod";
|
||||
import { schema } from "@/integrations/drizzle";
|
||||
import { resumeDataSchema } from "@/schema/resume/data";
|
||||
|
||||
const resumeSchema = createSelectSchema(schema.resume, {
|
||||
id: z.string().describe("The ID of the resume."),
|
||||
name: z.string().min(1).max(64).describe("The name of the resume."),
|
||||
slug: z.string().min(1).max(64).describe("The slug of the resume."),
|
||||
tags: z.array(z.string()).describe("The tags of the resume."),
|
||||
isPublic: z.boolean().describe("Whether the resume is public."),
|
||||
isLocked: z.boolean().describe("Whether the resume is locked."),
|
||||
password: z.string().min(6).max(64).nullable().describe("The password of the resume, if any."),
|
||||
data: resumeDataSchema,
|
||||
userId: z.string().describe("The ID of the user who owns the resume."),
|
||||
createdAt: z.date().describe("The date and time the resume was created."),
|
||||
updatedAt: z.date().describe("The date and time the resume was last updated."),
|
||||
});
|
||||
|
||||
export const resumeDto = {
|
||||
list: {
|
||||
input: z
|
||||
.object({
|
||||
tags: z.array(z.string()).optional().default([]),
|
||||
sort: z.enum(["lastUpdatedAt", "createdAt", "name"]).optional().default("lastUpdatedAt"),
|
||||
})
|
||||
.optional()
|
||||
.default({ tags: [], sort: "lastUpdatedAt" }),
|
||||
|
||||
output: z.array(resumeSchema.omit({ data: true, password: true, userId: true })),
|
||||
},
|
||||
|
||||
getById: {
|
||||
input: resumeSchema.pick({ id: true }),
|
||||
output: resumeSchema
|
||||
.omit({ password: true, userId: true, createdAt: true, updatedAt: true })
|
||||
.extend({ hasPassword: z.boolean() }),
|
||||
},
|
||||
|
||||
getBySlug: {
|
||||
input: z.object({ username: z.string(), slug: z.string() }),
|
||||
output: resumeSchema.omit({ password: true, userId: true, createdAt: true, updatedAt: true }),
|
||||
},
|
||||
|
||||
create: {
|
||||
input: resumeSchema
|
||||
.pick({ name: true, slug: true, tags: true })
|
||||
.extend({ withSampleData: z.boolean().default(false) }),
|
||||
output: z.string().describe("The ID of the created resume."),
|
||||
},
|
||||
|
||||
import: {
|
||||
input: resumeSchema.pick({ data: true }),
|
||||
output: z.string().describe("The ID of the imported resume."),
|
||||
},
|
||||
|
||||
update: {
|
||||
input: resumeSchema
|
||||
.pick({ name: true, slug: true, tags: true, data: true, isPublic: true })
|
||||
.partial()
|
||||
.extend({ id: z.string() }),
|
||||
output: resumeSchema.omit({ password: true, userId: true, createdAt: true, updatedAt: true }),
|
||||
},
|
||||
|
||||
setLocked: {
|
||||
input: resumeSchema.pick({ id: true, isLocked: true }),
|
||||
output: z.void(),
|
||||
},
|
||||
|
||||
setPassword: {
|
||||
input: resumeSchema.pick({ id: true }).extend({ password: z.string().min(6).max(64) }),
|
||||
output: z.void(),
|
||||
},
|
||||
|
||||
removePassword: {
|
||||
input: resumeSchema.pick({ id: true }),
|
||||
output: z.void(),
|
||||
},
|
||||
|
||||
duplicate: {
|
||||
input: resumeSchema.pick({ id: true, name: true, slug: true, tags: true }),
|
||||
output: z.string().describe("The ID of the duplicated resume."),
|
||||
},
|
||||
|
||||
delete: {
|
||||
input: resumeSchema.pick({ id: true }),
|
||||
output: z.void(),
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import z from "zod";
|
||||
import { resumeDataSchema } from "@/schema/resume/data";
|
||||
import { sampleResumeData } from "@/schema/resume/sample";
|
||||
import { generateRandomName, slugify } from "@/utils/string";
|
||||
import { protectedProcedure, publicProcedure, serverOnlyProcedure } from "../context";
|
||||
import { resumeDto } from "../dto/resume";
|
||||
import { resumeService } from "../services/resume";
|
||||
|
||||
const tagsRouter = {
|
||||
@@ -63,29 +63,8 @@ export const resumeRouter = {
|
||||
summary: "List all resumes",
|
||||
description: "List of all the resumes for the authenticated user.",
|
||||
})
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
tags: z.array(z.string()).optional().default([]),
|
||||
sort: z.enum(["lastUpdatedAt", "createdAt", "name"]).optional().default("lastUpdatedAt"),
|
||||
})
|
||||
.optional()
|
||||
.default({ tags: [], sort: "lastUpdatedAt" }),
|
||||
)
|
||||
.output(
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
isPublic: z.boolean(),
|
||||
isLocked: z.boolean(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.input(resumeDto.list.input.optional().default({ tags: [], sort: "lastUpdatedAt" }))
|
||||
.output(resumeDto.list.output)
|
||||
.handler(async ({ input, context }) => {
|
||||
return await resumeService.list({
|
||||
userId: context.user.id,
|
||||
@@ -102,26 +81,15 @@ export const resumeRouter = {
|
||||
summary: "Get resume by ID",
|
||||
description: "Get a resume, along with its data, by its ID.",
|
||||
})
|
||||
.input(z.object({ id: z.string() }))
|
||||
.output(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
data: resumeDataSchema,
|
||||
isPublic: z.boolean(),
|
||||
isLocked: z.boolean(),
|
||||
hasPassword: z.boolean(),
|
||||
}),
|
||||
)
|
||||
.input(resumeDto.getById.input)
|
||||
.output(resumeDto.getById.output)
|
||||
.handler(async ({ context, input }) => {
|
||||
return await resumeService.getById({ id: input.id, userId: context.user.id });
|
||||
}),
|
||||
|
||||
getByIdForPrinter: serverOnlyProcedure
|
||||
.route({ tags: ["Internal"], summary: "Get resume by ID for printer" })
|
||||
.input(z.object({ id: z.string() }))
|
||||
.input(resumeDto.getById.input)
|
||||
.handler(async ({ input }) => {
|
||||
return await resumeService.getByIdForPrinter({ id: input.id });
|
||||
}),
|
||||
@@ -134,18 +102,8 @@ export const resumeRouter = {
|
||||
summary: "Get resume by username and slug",
|
||||
description: "Get a resume, along with its data, by its username and slug.",
|
||||
})
|
||||
.input(z.object({ username: z.string(), slug: z.string() }))
|
||||
.output(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
data: resumeDataSchema,
|
||||
isPublic: z.boolean(),
|
||||
isLocked: z.boolean(),
|
||||
}),
|
||||
)
|
||||
.input(resumeDto.getBySlug.input)
|
||||
.output(resumeDto.getBySlug.output)
|
||||
.handler(async ({ input, context }) => {
|
||||
return await resumeService.getBySlug({ ...input, currentUserId: context.user?.id });
|
||||
}),
|
||||
@@ -158,15 +116,8 @@ export const resumeRouter = {
|
||||
summary: "Create a new resume",
|
||||
description: "Create a new resume, with the ability to initialize it with sample data.",
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string().min(1).max(64),
|
||||
slug: z.string().min(1).max(64),
|
||||
tags: z.array(z.string()),
|
||||
withSampleData: z.boolean().default(false),
|
||||
}),
|
||||
)
|
||||
.output(z.string().describe("The ID of the created resume."))
|
||||
.input(resumeDto.create.input)
|
||||
.output(resumeDto.create.output)
|
||||
.errors({
|
||||
RESUME_SLUG_ALREADY_EXISTS: {
|
||||
message: "A resume with this slug already exists.",
|
||||
@@ -192,8 +143,8 @@ export const resumeRouter = {
|
||||
summary: "Import a resume",
|
||||
description: "Import a resume from a file.",
|
||||
})
|
||||
.input(z.object({ data: resumeDataSchema }))
|
||||
.output(z.string().describe("The ID of the imported resume."))
|
||||
.input(resumeDto.import.input)
|
||||
.output(resumeDto.import.output)
|
||||
.errors({
|
||||
RESUME_SLUG_ALREADY_EXISTS: {
|
||||
message: "A resume with this slug already exists.",
|
||||
@@ -222,17 +173,8 @@ export const resumeRouter = {
|
||||
summary: "Update a resume",
|
||||
description: "Update a resume, along with its data, by its ID.",
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
slug: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
data: resumeDataSchema.optional(),
|
||||
isPublic: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.output(z.void())
|
||||
.input(resumeDto.update.input)
|
||||
.output(resumeDto.update.output)
|
||||
.errors({
|
||||
RESUME_SLUG_ALREADY_EXISTS: {
|
||||
message: "A resume with this slug already exists.",
|
||||
@@ -259,8 +201,8 @@ export const resumeRouter = {
|
||||
summary: "Set resume locked status",
|
||||
description: "Toggle the locked status of a resume, by its ID.",
|
||||
})
|
||||
.input(z.object({ id: z.string(), isLocked: z.boolean() }))
|
||||
.output(z.void())
|
||||
.input(resumeDto.setLocked.input)
|
||||
.output(resumeDto.setLocked.output)
|
||||
.handler(async ({ context, input }) => {
|
||||
return await resumeService.setLocked({
|
||||
id: input.id,
|
||||
@@ -277,8 +219,8 @@ export const resumeRouter = {
|
||||
summary: "Set password on a resume",
|
||||
description: "Set a password on a resume to protect it from unauthorized access when shared publicly.",
|
||||
})
|
||||
.input(z.object({ id: z.string(), password: z.string().min(6).max(64) }))
|
||||
.output(z.void())
|
||||
.input(resumeDto.setPassword.input)
|
||||
.output(resumeDto.setPassword.output)
|
||||
.handler(async ({ context, input }) => {
|
||||
return await resumeService.setPassword({
|
||||
id: input.id,
|
||||
@@ -295,8 +237,8 @@ export const resumeRouter = {
|
||||
summary: "Remove password from a resume",
|
||||
description: "Remove password protection from a resume.",
|
||||
})
|
||||
.input(z.object({ id: z.string() }))
|
||||
.output(z.void())
|
||||
.input(resumeDto.removePassword.input)
|
||||
.output(resumeDto.removePassword.output)
|
||||
.handler(async ({ context, input }) => {
|
||||
return await resumeService.removePassword({
|
||||
id: input.id,
|
||||
@@ -312,15 +254,8 @@ export const resumeRouter = {
|
||||
summary: "Duplicate a resume",
|
||||
description: "Duplicate a resume, by its ID.",
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
slug: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
}),
|
||||
)
|
||||
.output(z.string().describe("The ID of the duplicated resume."))
|
||||
.input(resumeDto.duplicate.input)
|
||||
.output(resumeDto.duplicate.output)
|
||||
.handler(async ({ context, input }) => {
|
||||
const original = await resumeService.getById({ id: input.id, userId: context.user.id });
|
||||
|
||||
@@ -342,8 +277,8 @@ export const resumeRouter = {
|
||||
summary: "Delete a resume",
|
||||
description: "Delete a resume, by its ID.",
|
||||
})
|
||||
.input(z.object({ id: z.string() }))
|
||||
.output(z.void())
|
||||
.input(resumeDto.delete.input)
|
||||
.output(resumeDto.delete.output)
|
||||
.handler(async ({ context, input }) => {
|
||||
return await resumeService.delete({ id: input.id, userId: context.user.id });
|
||||
}),
|
||||
|
||||
@@ -14,7 +14,7 @@ import { hasResumeAccess } from "../helpers/resume-access";
|
||||
import { getStorageService } from "./storage";
|
||||
|
||||
const tags = {
|
||||
list: async (input: { userId: string }): Promise<string[]> => {
|
||||
list: async (input: { userId: string }) => {
|
||||
const result = await db
|
||||
.select({ tags: schema.resume.tags })
|
||||
.from(schema.resume)
|
||||
@@ -50,7 +50,7 @@ const statistics = {
|
||||
};
|
||||
},
|
||||
|
||||
increment: async (input: { id: string; views?: boolean; downloads?: boolean }): Promise<void> => {
|
||||
increment: async (input: { id: string; views?: boolean; downloads?: boolean }) => {
|
||||
const views = input.views ? 1 : 0;
|
||||
const downloads = input.downloads ? 1 : 0;
|
||||
const lastViewedAt = input.views ? sql`now()` : undefined;
|
||||
@@ -233,7 +233,7 @@ export const resumeService = {
|
||||
tags: string[];
|
||||
locale: Locale;
|
||||
data?: ResumeData;
|
||||
}): Promise<string> => {
|
||||
}) => {
|
||||
const id = generateId();
|
||||
|
||||
input.data = input.data ?? defaultResumeData;
|
||||
@@ -269,7 +269,7 @@ export const resumeService = {
|
||||
tags?: string[];
|
||||
data?: ResumeData;
|
||||
isPublic?: boolean;
|
||||
}): Promise<void> => {
|
||||
}) => {
|
||||
const [resume] = await db
|
||||
.select({ isLocked: schema.resume.isLocked })
|
||||
.from(schema.resume)
|
||||
@@ -286,7 +286,7 @@ export const resumeService = {
|
||||
};
|
||||
|
||||
try {
|
||||
await db
|
||||
const [resume] = await db
|
||||
.update(schema.resume)
|
||||
.set(updateData)
|
||||
.where(
|
||||
@@ -295,7 +295,19 @@ export const resumeService = {
|
||||
eq(schema.resume.isLocked, false),
|
||||
eq(schema.resume.userId, input.userId),
|
||||
),
|
||||
);
|
||||
)
|
||||
.returning({
|
||||
id: schema.resume.id,
|
||||
name: schema.resume.name,
|
||||
slug: schema.resume.slug,
|
||||
tags: schema.resume.tags,
|
||||
data: schema.resume.data,
|
||||
isPublic: schema.resume.isPublic,
|
||||
isLocked: schema.resume.isLocked,
|
||||
hasPassword: sql<boolean>`${schema.resume.password} IS NOT NULL`,
|
||||
});
|
||||
|
||||
return resume;
|
||||
} catch (error) {
|
||||
if (get(error, "cause.constraint") === "resume_slug_user_id_unique") {
|
||||
throw new ORPCError("RESUME_SLUG_ALREADY_EXISTS", { status: 400 });
|
||||
@@ -305,14 +317,14 @@ export const resumeService = {
|
||||
}
|
||||
},
|
||||
|
||||
setLocked: async (input: { id: string; userId: string; isLocked: boolean }): Promise<void> => {
|
||||
setLocked: async (input: { id: string; userId: string; isLocked: boolean }) => {
|
||||
await db
|
||||
.update(schema.resume)
|
||||
.set({ isLocked: input.isLocked })
|
||||
.where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId)));
|
||||
},
|
||||
|
||||
setPassword: async (input: { id: string; userId: string; password: string }): Promise<void> => {
|
||||
setPassword: async (input: { id: string; userId: string; password: string }) => {
|
||||
const hashedPassword = await hashPassword(input.password);
|
||||
|
||||
await db
|
||||
@@ -321,14 +333,14 @@ export const resumeService = {
|
||||
.where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId)));
|
||||
},
|
||||
|
||||
removePassword: async (input: { id: string; userId: string }): Promise<void> => {
|
||||
removePassword: async (input: { id: string; userId: string }) => {
|
||||
await db
|
||||
.update(schema.resume)
|
||||
.set({ password: null })
|
||||
.where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId)));
|
||||
},
|
||||
|
||||
delete: async (input: { id: string; userId: string }): Promise<void> => {
|
||||
delete: async (input: { id: string; userId: string }) => {
|
||||
const storageService = getStorageService();
|
||||
|
||||
const deleteResumePromise = db
|
||||
|
||||
Reference in New Issue
Block a user