+38
apps/server/prisma/migrations/20251025120015_add_cv_template_system/migration.sql
+38
apps/server/prisma/migrations/20251025120015_add_cv_template_system/migration.sql
···
1
+
-- CreateTable
2
+
CREATE TABLE "cv_templates" (
3
+
"id" TEXT NOT NULL,
4
+
"name" TEXT NOT NULL,
5
+
"description" TEXT,
6
+
"category" TEXT NOT NULL,
7
+
"isDefault" BOOLEAN NOT NULL DEFAULT false,
8
+
"isActive" BOOLEAN NOT NULL DEFAULT true,
9
+
"thumbnail" TEXT,
10
+
"config" JSONB NOT NULL,
11
+
"html" TEXT NOT NULL,
12
+
"css" TEXT NOT NULL,
13
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
14
+
"updatedAt" TIMESTAMP(3) NOT NULL,
15
+
16
+
CONSTRAINT "cv_templates_pkey" PRIMARY KEY ("id")
17
+
);
18
+
19
+
-- CreateTable
20
+
CREATE TABLE "cvs" (
21
+
"id" TEXT NOT NULL,
22
+
"userId" TEXT NOT NULL,
23
+
"templateId" TEXT NOT NULL,
24
+
"title" TEXT NOT NULL,
25
+
"content" JSONB NOT NULL,
26
+
"isPublic" BOOLEAN NOT NULL DEFAULT false,
27
+
"isActive" BOOLEAN NOT NULL DEFAULT true,
28
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
29
+
"updatedAt" TIMESTAMP(3) NOT NULL,
30
+
31
+
CONSTRAINT "cvs_pkey" PRIMARY KEY ("id")
32
+
);
33
+
34
+
-- AddForeignKey
35
+
ALTER TABLE "cvs" ADD CONSTRAINT "cvs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
36
+
37
+
-- AddForeignKey
38
+
ALTER TABLE "cvs" ADD CONSTRAINT "cvs_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "cv_templates"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+14
apps/server/prisma/migrations/20251026154204_simplify_cv_template_schema/migration.sql
+14
apps/server/prisma/migrations/20251026154204_simplify_cv_template_schema/migration.sql
···
1
+
-- AlterTable
2
+
ALTER TABLE "cv_templates" DROP COLUMN "category",
3
+
DROP COLUMN "config",
4
+
DROP COLUMN "css",
5
+
DROP COLUMN "html",
6
+
DROP COLUMN "isActive",
7
+
DROP COLUMN "isDefault",
8
+
DROP COLUMN "thumbnail";
9
+
10
+
-- AlterTable
11
+
ALTER TABLE "cvs" DROP COLUMN "content",
12
+
DROP COLUMN "isActive",
13
+
DROP COLUMN "isPublic";
14
+
+18
apps/server/prisma/migrations/20251026203242_add_cv_fields/migration.sql
+18
apps/server/prisma/migrations/20251026203242_add_cv_fields/migration.sql
···
1
+
-- AlterTable
2
+
ALTER TABLE "cvs" ADD COLUMN "education" JSONB,
3
+
ADD COLUMN "introduction" TEXT;
4
+
5
+
-- AlterTable
6
+
ALTER TABLE "memberships" RENAME CONSTRAINT "user_organizations_pkey" TO "memberships_pkey";
7
+
8
+
-- RenameForeignKey
9
+
ALTER TABLE "memberships" RENAME CONSTRAINT "user_organizations_organizationId_fkey" TO "memberships_organizationId_fkey";
10
+
11
+
-- RenameForeignKey
12
+
ALTER TABLE "memberships" RENAME CONSTRAINT "user_organizations_organizationRoleId_fkey" TO "memberships_organizationRoleId_fkey";
13
+
14
+
-- RenameForeignKey
15
+
ALTER TABLE "memberships" RENAME CONSTRAINT "user_organizations_userId_fkey" TO "memberships_userId_fkey";
16
+
17
+
-- RenameIndex
18
+
ALTER INDEX "user_organizations_userId_organizationId_key" RENAME TO "memberships_userId_organizationId_key";
+18
apps/server/src/modules/cv-template/cv-template.mapper.ts
+18
apps/server/src/modules/cv-template/cv-template.mapper.ts
···
1
+
import type { CVTemplate as PrismaCVTemplate } from "@prisma/client";
2
+
import { CVTemplate } from "./graphql/cv-template.type";
3
+
4
+
export const cvTemplateMapper = {
5
+
toDomain: (template: PrismaCVTemplate): CVTemplate => {
6
+
return new CVTemplate({
7
+
id: template.id,
8
+
name: template.name,
9
+
description: template.description,
10
+
createdAt: template.createdAt,
11
+
updatedAt: template.updatedAt,
12
+
});
13
+
},
14
+
15
+
mapToDomain: (templates: PrismaCVTemplate[]): CVTemplate[] => {
16
+
return templates.map((template) => cvTemplateMapper.toDomain(template));
17
+
},
18
+
};
+23
apps/server/src/modules/cv-template/cv-template.module.ts
+23
apps/server/src/modules/cv-template/cv-template.module.ts
···
1
+
import { Module } from "@nestjs/common";
2
+
import { AuthModule } from "@/modules/auth/auth.module";
3
+
import { BaseModule } from "@/modules/base/base.module";
4
+
import { DatabaseModule } from "@/modules/database/database.module";
5
+
import { CVService } from "./cv.service";
6
+
import { CVTemplateService } from "./cv-template.service";
7
+
import { CVResolver, CVTemplateResolver } from "./graphql/cv-template.resolver";
8
+
import { CVUserFieldResolver } from "./graphql/user-field.resolver";
9
+
import { CVTemplateSeedService } from "./seed/cv-template.seed";
10
+
11
+
@Module({
12
+
imports: [DatabaseModule, BaseModule, AuthModule],
13
+
providers: [
14
+
CVTemplateService,
15
+
CVService,
16
+
CVTemplateResolver,
17
+
CVResolver,
18
+
CVTemplateSeedService,
19
+
CVUserFieldResolver,
20
+
],
21
+
exports: [CVTemplateService, CVService],
22
+
})
23
+
export class CVTemplateModule {}
+36
apps/server/src/modules/cv-template/cv-template.service.ts
+36
apps/server/src/modules/cv-template/cv-template.service.ts
···
1
+
import { Injectable } from "@nestjs/common";
2
+
import { notFound } from "@/modules/base/not-found.util";
3
+
import { PrismaService } from "@/modules/database/prisma.service";
4
+
import { cvTemplateMapper } from "./cv-template.mapper";
5
+
6
+
type CVTemplateFilters = Record<string, never>;
7
+
8
+
@Injectable()
9
+
export class CVTemplateService {
10
+
constructor(private readonly prisma: PrismaService) {}
11
+
12
+
async findMany(filters: CVTemplateFilters = {}) {
13
+
const templates = await this.prisma["cVTemplate"].findMany({
14
+
orderBy: { createdAt: "desc" },
15
+
});
16
+
17
+
return cvTemplateMapper.mapToDomain(templates);
18
+
}
19
+
20
+
async count(filters: CVTemplateFilters = {}) {
21
+
return this.prisma["cVTemplate"].count();
22
+
}
23
+
24
+
async findById(id: string) {
25
+
const template = await this.prisma["cVTemplate"].findUnique({
26
+
where: { id },
27
+
});
28
+
29
+
return template ? cvTemplateMapper.toDomain(template) : null;
30
+
}
31
+
32
+
async findByIdOrFail(id: string) {
33
+
const template = await this.findById(id);
34
+
return template ?? notFound("CVTemplate", "id", id);
35
+
}
36
+
}
+27
apps/server/src/modules/cv-template/cv.mapper.ts
+27
apps/server/src/modules/cv-template/cv.mapper.ts
···
1
+
import type {
2
+
CV as PrismaCV,
3
+
CVTemplate as PrismaCVTemplate,
4
+
} from "@prisma/client";
5
+
import { cvTemplateMapper } from "./cv-template.mapper";
6
+
import { CV } from "./graphql/cv.type";
7
+
8
+
export type PrismaCVWithTemplate = PrismaCV & {
9
+
template: PrismaCVTemplate;
10
+
};
11
+
12
+
export const cvMapper = {
13
+
toDomain: (cv: PrismaCVWithTemplate): CV => {
14
+
return new CV({
15
+
id: cv.id,
16
+
title: cv.title,
17
+
introduction: cv.introduction ?? null,
18
+
template: cvTemplateMapper.toDomain(cv.template),
19
+
createdAt: cv.createdAt,
20
+
updatedAt: cv.updatedAt,
21
+
});
22
+
},
23
+
24
+
mapToDomain: (cvs: PrismaCVWithTemplate[]): CV[] => {
25
+
return cvs.map((cv) => cvMapper.toDomain(cv));
26
+
},
27
+
};
+97
apps/server/src/modules/cv-template/cv.service.ts
+97
apps/server/src/modules/cv-template/cv.service.ts
···
1
+
import { Injectable } from "@nestjs/common";
2
+
import { notFound } from "@/modules/base/not-found.util";
3
+
import { PrismaService } from "@/modules/database/prisma.service";
4
+
import { cvMapper } from "./cv.mapper";
5
+
6
+
type CVFilters = {
7
+
userId: string;
8
+
};
9
+
10
+
@Injectable()
11
+
export class CVService {
12
+
constructor(private readonly prisma: PrismaService) {}
13
+
14
+
async findMany(filters: CVFilters) {
15
+
const cvs = await this.prisma["cV"].findMany({
16
+
where: { userId: filters.userId },
17
+
orderBy: { updatedAt: "desc" },
18
+
include: {
19
+
template: true,
20
+
},
21
+
});
22
+
23
+
return cvMapper.mapToDomain(cvs);
24
+
}
25
+
26
+
async count(filters: CVFilters) {
27
+
return this.prisma["cV"].count({
28
+
where: { userId: filters.userId },
29
+
});
30
+
}
31
+
32
+
async findById(id: string) {
33
+
const cv = await this.prisma["cV"].findUnique({
34
+
where: { id },
35
+
include: {
36
+
template: true,
37
+
},
38
+
});
39
+
40
+
return cv ? cvMapper.toDomain(cv) : null;
41
+
}
42
+
43
+
async findByIdOrFail(id: string) {
44
+
const cv = await this.findById(id);
45
+
return cv ?? notFound("CV", "id", id);
46
+
}
47
+
48
+
async create(
49
+
userId: string,
50
+
data: {
51
+
templateId: string;
52
+
title: string;
53
+
},
54
+
) {
55
+
const cv = await this.prisma["cV"].create({
56
+
data: {
57
+
userId,
58
+
templateId: data.templateId,
59
+
title: data.title,
60
+
},
61
+
include: {
62
+
template: true,
63
+
},
64
+
});
65
+
66
+
return cvMapper.toDomain(cv);
67
+
}
68
+
69
+
async update(
70
+
id: string,
71
+
data: {
72
+
title?: string;
73
+
},
74
+
) {
75
+
const updateData: Record<string, unknown> = {};
76
+
77
+
if (data.title !== undefined) {
78
+
updateData["title"] = data.title;
79
+
}
80
+
81
+
const cv = await this.prisma["cV"].update({
82
+
where: { id },
83
+
data: updateData,
84
+
include: {
85
+
template: true,
86
+
},
87
+
});
88
+
89
+
return cvMapper.toDomain(cv);
90
+
}
91
+
92
+
async delete(id: string): Promise<void> {
93
+
await this.prisma["cV"].delete({
94
+
where: { id },
95
+
});
96
+
}
97
+
}
+16
apps/server/src/modules/cv-template/graphql/cv-args.type.ts
+16
apps/server/src/modules/cv-template/graphql/cv-args.type.ts
···
1
+
import { ArgsType, Field, Int } from "@nestjs/graphql";
2
+
3
+
@ArgsType()
4
+
export class CVArgs {
5
+
@Field(() => Int, { nullable: true })
6
+
first?: number | null;
7
+
8
+
@Field(() => String, { nullable: true })
9
+
after?: string | null;
10
+
11
+
@Field(() => Int, { nullable: true })
12
+
last?: number | null;
13
+
14
+
@Field(() => String, { nullable: true })
15
+
before?: string | null;
16
+
}
+35
apps/server/src/modules/cv-template/graphql/cv-connection.type.ts
+35
apps/server/src/modules/cv-template/graphql/cv-connection.type.ts
···
1
+
import { Field, Int, ObjectType } from "@nestjs/graphql";
2
+
import { PageInfo, PaginationResult } from "@/modules/base/pagination.types";
3
+
import { CV } from "./cv.type";
4
+
import { CVEdge } from "./cv-edge.type";
5
+
6
+
@ObjectType()
7
+
export class CVConnection {
8
+
@Field(() => [CVEdge])
9
+
edges: CVEdge[];
10
+
11
+
@Field(() => PageInfo)
12
+
pageInfo: PageInfo;
13
+
14
+
@Field(() => Int)
15
+
totalCount: number;
16
+
17
+
constructor(edges: CVEdge[], pageInfo: PageInfo, totalCount: number) {
18
+
this.edges = edges;
19
+
this.pageInfo = pageInfo;
20
+
this.totalCount = totalCount;
21
+
}
22
+
23
+
/**
24
+
* Static factory method to create a connection from pagination result
25
+
*/
26
+
static fromPaginationResult(result: PaginationResult<CV>): CVConnection {
27
+
const edges = result.edges.map((edge) =>
28
+
CVEdge.fromPaginationEdge({
29
+
cursor: edge.cursor,
30
+
node: edge.node,
31
+
}),
32
+
);
33
+
return new CVConnection(edges, result.pageInfo, result.totalCount);
34
+
}
35
+
}
+13
apps/server/src/modules/cv-template/graphql/cv-edge.type.ts
+13
apps/server/src/modules/cv-template/graphql/cv-edge.type.ts
···
1
+
import { Field, ObjectType } from "@nestjs/graphql";
2
+
import { GraphQLString } from "graphql";
3
+
import { BaseEdge } from "@/modules/base/connection.types";
4
+
import { CV } from "./cv.type";
5
+
6
+
@ObjectType()
7
+
export class CVEdge extends BaseEdge<CV> {
8
+
@Field(() => GraphQLString)
9
+
declare cursor: string;
10
+
11
+
@Field(() => CV)
12
+
declare node: CV;
13
+
}
+16
apps/server/src/modules/cv-template/graphql/cv-input.type.ts
+16
apps/server/src/modules/cv-template/graphql/cv-input.type.ts
···
1
+
import { Field, InputType } from "@nestjs/graphql";
2
+
3
+
@InputType()
4
+
export class CreateCVInput {
5
+
@Field(() => String)
6
+
templateId!: string;
7
+
8
+
@Field(() => String)
9
+
title!: string;
10
+
}
11
+
12
+
@InputType()
13
+
export class UpdateCVInput {
14
+
@Field(() => String, { nullable: true })
15
+
title?: string;
16
+
}
+16
apps/server/src/modules/cv-template/graphql/cv-template-args.type.ts
+16
apps/server/src/modules/cv-template/graphql/cv-template-args.type.ts
···
1
+
import { ArgsType, Field, Int } from "@nestjs/graphql";
2
+
3
+
@ArgsType()
4
+
export class CVTemplateArgs {
5
+
@Field(() => Int, { nullable: true })
6
+
first?: number | null;
7
+
8
+
@Field(() => String, { nullable: true })
9
+
after?: string | null;
10
+
11
+
@Field(() => Int, { nullable: true })
12
+
last?: number | null;
13
+
14
+
@Field(() => String, { nullable: true })
15
+
before?: string | null;
16
+
}
+37
apps/server/src/modules/cv-template/graphql/cv-template-connection.type.ts
+37
apps/server/src/modules/cv-template/graphql/cv-template-connection.type.ts
···
1
+
import { Field, Int, ObjectType } from "@nestjs/graphql";
2
+
import { PageInfo, PaginationResult } from "@/modules/base/pagination.types";
3
+
import { CVTemplate } from "./cv-template.type";
4
+
import { CVTemplateEdge } from "./cv-template-edge.type";
5
+
6
+
@ObjectType()
7
+
export class CVTemplateConnection {
8
+
@Field(() => [CVTemplateEdge])
9
+
edges: CVTemplateEdge[];
10
+
11
+
@Field(() => PageInfo)
12
+
pageInfo: PageInfo;
13
+
14
+
@Field(() => Int)
15
+
totalCount: number;
16
+
17
+
constructor(edges: CVTemplateEdge[], pageInfo: PageInfo, totalCount: number) {
18
+
this.edges = edges;
19
+
this.pageInfo = pageInfo;
20
+
this.totalCount = totalCount;
21
+
}
22
+
23
+
/**
24
+
* Static factory method to create a connection from pagination result
25
+
*/
26
+
static fromPaginationResult(
27
+
result: PaginationResult<CVTemplate>,
28
+
): CVTemplateConnection {
29
+
const edges = result.edges.map((edge) =>
30
+
CVTemplateEdge.fromPaginationEdge({
31
+
cursor: edge.cursor,
32
+
node: edge.node,
33
+
}),
34
+
);
35
+
return new CVTemplateConnection(edges, result.pageInfo, result.totalCount);
36
+
}
37
+
}
+13
apps/server/src/modules/cv-template/graphql/cv-template-edge.type.ts
+13
apps/server/src/modules/cv-template/graphql/cv-template-edge.type.ts
···
1
+
import { Field, ObjectType } from "@nestjs/graphql";
2
+
import { GraphQLString } from "graphql";
3
+
import { BaseEdge } from "@/modules/base/connection.types";
4
+
import { CVTemplate } from "./cv-template.type";
5
+
6
+
@ObjectType()
7
+
export class CVTemplateEdge extends BaseEdge<CVTemplate> {
8
+
@Field(() => GraphQLString)
9
+
declare cursor: string;
10
+
11
+
@Field(() => CVTemplate)
12
+
declare node: CVTemplate;
13
+
}
+105
apps/server/src/modules/cv-template/graphql/cv-template.resolver.ts
+105
apps/server/src/modules/cv-template/graphql/cv-template.resolver.ts
···
1
+
import { UseGuards } from "@nestjs/common";
2
+
import { Args, Context, Mutation, Query, Resolver } from "@nestjs/graphql";
3
+
import { JwtAuthGuard } from "@/modules/auth/jwt-auth.guard";
4
+
import { PaginationService } from "@/modules/base/pagination.service";
5
+
import { CVService } from "../cv.service";
6
+
import { CVTemplateService } from "../cv-template.service";
7
+
import { CV } from "./cv.type";
8
+
import { CVArgs } from "./cv-args.type";
9
+
import { CVConnection } from "./cv-connection.type";
10
+
import { CreateCVInput, UpdateCVInput } from "./cv-input.type";
11
+
import { CVTemplate } from "./cv-template.type";
12
+
import { CVTemplateArgs } from "./cv-template-args.type";
13
+
import { CVTemplateConnection } from "./cv-template-connection.type";
14
+
15
+
@Resolver(() => CVTemplate)
16
+
export class CVTemplateResolver {
17
+
constructor(
18
+
private readonly cvTemplateService: CVTemplateService,
19
+
private readonly paginationService: PaginationService,
20
+
) {}
21
+
22
+
@Query(() => CVTemplateConnection)
23
+
async cvTemplates(
24
+
@Args() args: CVTemplateArgs = {},
25
+
): Promise<CVTemplateConnection> {
26
+
const options = this.paginationService.parsePaginationArgs(args);
27
+
28
+
const [items, totalCount] = await Promise.all([
29
+
this.cvTemplateService.findMany({}),
30
+
this.cvTemplateService.count({}),
31
+
]);
32
+
33
+
const result = this.paginationService.buildPaginationResult(
34
+
items,
35
+
totalCount,
36
+
options,
37
+
);
38
+
39
+
return CVTemplateConnection.fromPaginationResult(result);
40
+
}
41
+
42
+
@Query(() => CVTemplate, { nullable: true })
43
+
async cvTemplate(@Args("id") id: string) {
44
+
return this.cvTemplateService.findById(id);
45
+
}
46
+
}
47
+
48
+
@Resolver(() => CV)
49
+
export class CVResolver {
50
+
constructor(
51
+
private readonly cvService: CVService,
52
+
private readonly paginationService: PaginationService,
53
+
) {}
54
+
55
+
@Query(() => CVConnection)
56
+
@UseGuards(JwtAuthGuard)
57
+
async myCVs(
58
+
@Args() args: CVArgs = {},
59
+
@Context() context: { req: { user: { id: string } } },
60
+
): Promise<CVConnection> {
61
+
const userId = context.req.user.id;
62
+
const options = this.paginationService.parsePaginationArgs(args);
63
+
64
+
const [items, totalCount] = await Promise.all([
65
+
this.cvService.findMany({ userId }),
66
+
this.cvService.count({ userId }),
67
+
]);
68
+
69
+
const result = this.paginationService.buildPaginationResult(
70
+
items,
71
+
totalCount,
72
+
options,
73
+
);
74
+
75
+
return CVConnection.fromPaginationResult(result);
76
+
}
77
+
78
+
@Query(() => CV, { nullable: true })
79
+
@UseGuards(JwtAuthGuard)
80
+
async cv(@Args("id") id: string) {
81
+
return this.cvService.findById(id);
82
+
}
83
+
84
+
@Mutation(() => CV)
85
+
@UseGuards(JwtAuthGuard)
86
+
async createCV(
87
+
@Args("input") input: CreateCVInput,
88
+
@Context() context: { req: { user: { id: string } } },
89
+
) {
90
+
const userId = context.req.user.id;
91
+
return this.cvService.create(userId, input);
92
+
}
93
+
94
+
@Mutation(() => CV)
95
+
@UseGuards(JwtAuthGuard)
96
+
async updateCV(@Args("id") id: string, @Args("input") input: UpdateCVInput) {
97
+
return this.cvService.update(id, input);
98
+
}
99
+
100
+
@Mutation(() => Boolean)
101
+
@UseGuards(JwtAuthGuard)
102
+
async deleteCV(@Args("id") id: string) {
103
+
return this.cvService.delete(id);
104
+
}
105
+
}
+33
apps/server/src/modules/cv-template/graphql/cv-template.type.ts
+33
apps/server/src/modules/cv-template/graphql/cv-template.type.ts
···
1
+
import { Field, ID, ObjectType } from "@nestjs/graphql";
2
+
3
+
@ObjectType()
4
+
export class CVTemplate {
5
+
@Field(() => ID)
6
+
id: string;
7
+
8
+
@Field(() => Date)
9
+
createdAt: Date;
10
+
11
+
@Field(() => Date)
12
+
updatedAt: Date;
13
+
14
+
@Field(() => String)
15
+
name: string;
16
+
17
+
@Field(() => String, { nullable: true })
18
+
description?: string | null;
19
+
20
+
constructor(data: {
21
+
id: string;
22
+
name: string;
23
+
description?: string | null;
24
+
createdAt: Date;
25
+
updatedAt: Date;
26
+
}) {
27
+
this.id = data.id;
28
+
this.createdAt = data.createdAt;
29
+
this.updatedAt = data.updatedAt;
30
+
this.name = data.name;
31
+
this.description = data.description ?? null;
32
+
}
33
+
}
+39
apps/server/src/modules/cv-template/graphql/cv.type.ts
+39
apps/server/src/modules/cv-template/graphql/cv.type.ts
···
1
+
import { Field, ID, ObjectType } from "@nestjs/graphql";
2
+
import { CVTemplate } from "./cv-template.type";
3
+
4
+
@ObjectType()
5
+
export class CV {
6
+
@Field(() => ID)
7
+
id: string;
8
+
9
+
@Field(() => Date)
10
+
createdAt: Date;
11
+
12
+
@Field(() => Date)
13
+
updatedAt: Date;
14
+
15
+
@Field(() => String)
16
+
title: string;
17
+
18
+
@Field(() => String, { nullable: true })
19
+
introduction: string | null;
20
+
21
+
@Field(() => CVTemplate)
22
+
template: CVTemplate;
23
+
24
+
constructor(data: {
25
+
id: string;
26
+
title: string;
27
+
introduction?: string | null;
28
+
template: CVTemplate;
29
+
createdAt: Date;
30
+
updatedAt: Date;
31
+
}) {
32
+
this.id = data.id;
33
+
this.createdAt = data.createdAt;
34
+
this.updatedAt = data.updatedAt;
35
+
this.title = data.title;
36
+
this.introduction = data.introduction ?? null;
37
+
this.template = data.template;
38
+
}
39
+
}
+35
apps/server/src/modules/cv-template/graphql/user-field.resolver.ts
+35
apps/server/src/modules/cv-template/graphql/user-field.resolver.ts
···
1
+
import { UseGuards } from "@nestjs/common";
2
+
import { Args, Parent, ResolveField, Resolver } from "@nestjs/graphql";
3
+
import { JwtAuthGuard } from "@/modules/auth/jwt-auth.guard";
4
+
import { PaginationService } from "@/modules/base/pagination.service";
5
+
import { User } from "@/modules/user/user.type";
6
+
import { CVService } from "../cv.service";
7
+
import { CVArgs } from "./cv-args.type";
8
+
import { CVConnection } from "./cv-connection.type";
9
+
10
+
@Resolver(() => User)
11
+
@UseGuards(JwtAuthGuard)
12
+
export class CVUserFieldResolver {
13
+
constructor(
14
+
private readonly cvService: CVService,
15
+
private readonly paginationService: PaginationService,
16
+
) {}
17
+
18
+
@ResolveField(() => CVConnection, { nullable: true })
19
+
async cvs(
20
+
@Parent() user: User,
21
+
@Args() args: CVArgs = {},
22
+
): Promise<CVConnection> {
23
+
const options = this.paginationService.parsePaginationArgs(args);
24
+
const [items, totalCount] = await Promise.all([
25
+
this.cvService.findMany({ userId: user.id }),
26
+
this.cvService.count({ userId: user.id }),
27
+
]);
28
+
const result = this.paginationService.buildPaginationResult(
29
+
items,
30
+
totalCount,
31
+
options,
32
+
);
33
+
return CVConnection.fromPaginationResult(result);
34
+
}
35
+
}