because I got bored of customising my CV for every job
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(server): extract GraphQL types and resolvers to separate modules

+1746
+9
apps/server/src/modules/job-experience/company/graphql/company-connection-args.type.ts
··· 1 + import { ArgsType, Field } from "@nestjs/graphql"; 2 + import { GraphQLString } from "graphql"; 3 + import { BasePaginationArgs } from "@/modules/base/pagination.types"; 4 + 5 + @ArgsType() 6 + export class CompanyConnectionArgs extends BasePaginationArgs { 7 + @Field(() => GraphQLString, { nullable: true }) 8 + searchTerm?: string | null; 9 + }
+38
apps/server/src/modules/job-experience/company/graphql/company-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import { Company as CompanyDomain } from "@/modules/job-experience/company/company.entity"; 4 + import { Company } from "./company.type"; 5 + import { CompanyEdge } from "./company-edge.type"; 6 + 7 + @ObjectType() 8 + export class CompanyConnection { 9 + @Field(() => [CompanyEdge]) 10 + edges: CompanyEdge[]; 11 + 12 + @Field(() => PageInfo) 13 + pageInfo: PageInfo; 14 + 15 + @Field(() => Int) 16 + totalCount: number; 17 + 18 + constructor(edges: CompanyEdge[], pageInfo: PageInfo, totalCount: number) { 19 + this.edges = edges; 20 + this.pageInfo = pageInfo; 21 + this.totalCount = totalCount; 22 + } 23 + 24 + /** 25 + * Static factory method to create a connection from pagination result 26 + */ 27 + static fromPaginationResult( 28 + result: PaginationResult<CompanyDomain>, 29 + ): CompanyConnection { 30 + const edges = result.edges.map((edge) => 31 + CompanyEdge.fromPaginationEdge({ 32 + cursor: edge.cursor, 33 + node: Company.fromDomain(edge.node), 34 + }), 35 + ); 36 + return new CompanyConnection(edges, result.pageInfo, result.totalCount); 37 + } 38 + }
+13
apps/server/src/modules/job-experience/company/graphql/company-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 { Company } from "./company.type"; 5 + 6 + @ObjectType() 7 + export class CompanyEdge extends BaseEdge<Company> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => Company) 12 + declare node: Company; 13 + }
+90
apps/server/src/modules/job-experience/company/graphql/company.resolver.ts
··· 1 + import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 2 + import { PaginationService } from "@/modules/base/pagination.service"; 3 + import { CompanyService } from "@/modules/job-experience/company/company.service"; 4 + import { Company } from "./company.type"; 5 + import { CompanyConnection } from "./company-connection.type"; 6 + import { CompanyConnectionArgs } from "./company-connection-args.type"; 7 + 8 + @Resolver(() => Company) 9 + export class CompanyResolver { 10 + constructor( 11 + private readonly companyService: CompanyService, 12 + private readonly paginationService: PaginationService, 13 + ) {} 14 + 15 + @Query(() => CompanyConnection) 16 + async companies( 17 + @Args() args: CompanyConnectionArgs = {}, 18 + ): Promise<CompanyConnection> { 19 + const options = this.paginationService.parsePaginationArgs(args); 20 + const filters = { searchTerm: args.searchTerm || undefined }; 21 + 22 + const [items, totalCount] = await Promise.all([ 23 + this.companyService.findMany(filters), 24 + this.companyService.count(filters), 25 + ]); 26 + 27 + const result = this.paginationService.buildPaginationResult( 28 + items, 29 + totalCount, 30 + options, 31 + ); 32 + 33 + return CompanyConnection.fromPaginationResult(result); 34 + } 35 + 36 + @Query(() => Company) 37 + async company(@Args("id") id: string): Promise<Company> { 38 + const domainCompany = await this.companyService.findByIdOrFail(id); 39 + return Company.fromDomain(domainCompany); 40 + } 41 + 42 + @Mutation(() => Company) 43 + async createCompany( 44 + @Args("name") name: string, 45 + @Args("description", { nullable: true }) description?: string, 46 + @Args("website", { nullable: true }) website?: string, 47 + ): Promise<Company> { 48 + const createData: { name: string; description?: string; website?: string } = 49 + { name }; 50 + if (description !== undefined) { 51 + createData.description = description; 52 + } 53 + if (website !== undefined) { 54 + createData.website = website; 55 + } 56 + const domainCompany = await this.companyService.create(createData); 57 + return Company.fromDomain(domainCompany); 58 + } 59 + 60 + @Mutation(() => Company) 61 + async updateCompany( 62 + @Args("id") id: string, 63 + @Args("name", { nullable: true }) name?: string, 64 + @Args("description", { nullable: true }) description?: string, 65 + @Args("website", { nullable: true }) website?: string, 66 + ): Promise<Company> { 67 + const updateData: { 68 + name?: string; 69 + description?: string; 70 + website?: string; 71 + } = {}; 72 + if (name !== undefined) { 73 + updateData.name = name; 74 + } 75 + if (description !== undefined) { 76 + updateData.description = description; 77 + } 78 + if (website !== undefined) { 79 + updateData.website = website; 80 + } 81 + const domainCompany = await this.companyService.update(id, updateData); 82 + return Company.fromDomain(domainCompany); 83 + } 84 + 85 + @Mutation(() => Boolean) 86 + async deleteCompany(@Args("id") id: string): Promise<boolean> { 87 + await this.companyService.delete(id); 88 + return true; 89 + } 90 + }
+54
apps/server/src/modules/job-experience/company/graphql/company.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import type { Company as DomainCompany } from "@/modules/job-experience/company/company.entity"; 3 + 4 + @ObjectType() 5 + export class Company { 6 + @Field(() => ID) 7 + id: string; 8 + 9 + @Field() 10 + name: string; 11 + 12 + @Field({ nullable: true }) 13 + description?: string; 14 + 15 + @Field({ nullable: true }) 16 + website?: string; 17 + 18 + @Field() 19 + createdAt: Date; 20 + 21 + @Field() 22 + updatedAt: Date; 23 + 24 + constructor( 25 + id: string, 26 + name: string, 27 + createdAt: Date, 28 + updatedAt: Date, 29 + description?: string, 30 + website?: string, 31 + ) { 32 + this.id = id; 33 + this.name = name; 34 + this.createdAt = createdAt; 35 + this.updatedAt = updatedAt; 36 + if (description !== undefined) { 37 + this.description = description; 38 + } 39 + if (website !== undefined) { 40 + this.website = website; 41 + } 42 + } 43 + 44 + static fromDomain(domainCompany: DomainCompany): Company { 45 + return new Company( 46 + domainCompany.id, 47 + domainCompany.name, 48 + domainCompany.createdAt, 49 + domainCompany.updatedAt, 50 + domainCompany.description, 51 + domainCompany.website, 52 + ); 53 + } 54 + }
+158
apps/server/src/modules/job-experience/employment/graphql/employment.resolver.ts
··· 1 + import { UseGuards } from "@nestjs/common"; 2 + import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 3 + import { CurrentUser } from "@/modules/auth/current-user.decorator"; 4 + import { JwtAuthGuard } from "@/modules/auth/jwt-auth.guard"; 5 + import { CompanyService } from "@/modules/job-experience/company/company.service"; 6 + import type { 7 + CreateUserJobExperienceDto, 8 + UpdateUserJobExperienceDto, 9 + } from "@/modules/job-experience/employment/user-job-experience.dto"; 10 + import { UserJobExperienceService } from "@/modules/job-experience/employment/user-job-experience.service"; 11 + import { LevelService } from "@/modules/job-experience/level/level.service"; 12 + import { RoleService } from "@/modules/job-experience/role/role.service"; 13 + import { SkillService } from "@/modules/job-experience/skill/skill.service"; 14 + import type { User } from "@/modules/user/user.type"; 15 + import { UserJobExperience } from "./user-job-experience.type"; 16 + 17 + @Resolver(() => UserJobExperience) 18 + @UseGuards(JwtAuthGuard) 19 + export class EmploymentResolver { 20 + constructor( 21 + private readonly userJobExperienceService: UserJobExperienceService, 22 + private readonly companyService: CompanyService, 23 + private readonly roleService: RoleService, 24 + private readonly levelService: LevelService, 25 + private readonly skillService: SkillService, 26 + ) {} 27 + 28 + @Query(() => [UserJobExperience]) 29 + async myEmploymentHistory( 30 + @CurrentUser() user: User, 31 + ): Promise<UserJobExperience[]> { 32 + const domainExperiences = 33 + await this.userJobExperienceService.findForUser(user); 34 + return domainExperiences.map((exp) => UserJobExperience.fromDomain(exp)); 35 + } 36 + 37 + @Mutation(() => UserJobExperience) 38 + async createJobExperience( 39 + @CurrentUser() user: User, 40 + @Args("companyId") companyId: string, 41 + @Args("roleId") roleId: string, 42 + @Args("levelId") levelId: string, 43 + @Args("startDate") startDate: Date, 44 + @Args("endDate", { nullable: true }) endDate?: Date, 45 + @Args("description", { nullable: true }) description?: string, 46 + @Args("skillIds", { type: () => [String], nullable: true }) 47 + skillIds?: string[], 48 + ): Promise<UserJobExperience> { 49 + // Fetch full entities 50 + const company = await this.companyService.findByIdOrFail(companyId); 51 + const role = await this.roleService.findByIdOrFail(roleId); 52 + const level = await this.levelService.findByIdOrFail(levelId); 53 + const skills = skillIds 54 + ? await Promise.all( 55 + skillIds.map((id) => this.skillService.findByIdOrFail(id)), 56 + ) 57 + : undefined; 58 + 59 + const createData: CreateUserJobExperienceDto = { 60 + user, 61 + company, 62 + role, 63 + level, 64 + startDate, 65 + }; 66 + 67 + if (endDate !== undefined) { 68 + createData.endDate = endDate; 69 + } 70 + if (description !== undefined) { 71 + createData.description = description; 72 + } 73 + if (skills !== undefined) { 74 + createData.skills = skills; 75 + } 76 + 77 + const domainExperience = 78 + await this.userJobExperienceService.create(createData); 79 + 80 + return UserJobExperience.fromDomain(domainExperience); 81 + } 82 + 83 + @Mutation(() => UserJobExperience) 84 + async updateJobExperience( 85 + @Args("id") id: string, 86 + @Args("companyId", { nullable: true }) companyId?: string, 87 + @Args("roleId", { nullable: true }) roleId?: string, 88 + @Args("levelId", { nullable: true }) levelId?: string, 89 + @Args("startDate", { nullable: true }) startDate?: Date, 90 + @Args("endDate", { nullable: true }) endDate?: Date, 91 + @Args("description", { nullable: true }) description?: string, 92 + @Args("skillIds", { type: () => [String], nullable: true }) 93 + skillIds?: string[], 94 + ): Promise<UserJobExperience> { 95 + const updateData: UpdateUserJobExperienceDto = {}; 96 + 97 + if (companyId !== undefined) { 98 + updateData.company = await this.companyService.findByIdOrFail(companyId); 99 + } 100 + if (roleId !== undefined) { 101 + updateData.role = await this.roleService.findByIdOrFail(roleId); 102 + } 103 + if (levelId !== undefined) { 104 + updateData.level = await this.levelService.findByIdOrFail(levelId); 105 + } 106 + if (startDate !== undefined) { 107 + updateData.startDate = startDate; 108 + } 109 + if (endDate !== undefined) { 110 + updateData.endDate = endDate; 111 + } 112 + if (description !== undefined) { 113 + updateData.description = description; 114 + } 115 + if (skillIds !== undefined) { 116 + updateData.skills = await Promise.all( 117 + skillIds.map((id) => this.skillService.findByIdOrFail(id)), 118 + ); 119 + } 120 + 121 + const domainExperience = await this.userJobExperienceService.update( 122 + id, 123 + updateData, 124 + ); 125 + 126 + return UserJobExperience.fromDomain(domainExperience); 127 + } 128 + 129 + @Mutation(() => Boolean) 130 + async deleteJobExperience(@Args("id") id: string): Promise<boolean> { 131 + await this.userJobExperienceService.delete(id); 132 + return true; 133 + } 134 + 135 + @Mutation(() => UserJobExperience) 136 + async addSkillsToJobExperience( 137 + @Args("experienceId") experienceId: string, 138 + @Args("skillIds", { type: () => [String] }) skillIds: string[], 139 + ): Promise<UserJobExperience> { 140 + const domainExperience = await this.userJobExperienceService.addSkills( 141 + experienceId, 142 + skillIds, 143 + ); 144 + return UserJobExperience.fromDomain(domainExperience); 145 + } 146 + 147 + @Mutation(() => UserJobExperience) 148 + async removeSkillsFromJobExperience( 149 + @Args("experienceId") experienceId: string, 150 + @Args("skillIds", { type: () => [String] }) skillIds: string[], 151 + ): Promise<UserJobExperience> { 152 + const domainExperience = await this.userJobExperienceService.removeSkills( 153 + experienceId, 154 + skillIds, 155 + ); 156 + return UserJobExperience.fromDomain(domainExperience); 157 + } 158 + }
+30
apps/server/src/modules/job-experience/employment/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 { PaginationArgs } from "@/modules/base/pagination.types"; 6 + import { UserJobExperienceService } from "@/modules/job-experience/employment/user-job-experience.service"; 7 + import { User } from "@/modules/user/user.type"; 8 + import { UserJobExperienceConnection } from "./user-job-experience-connection.type"; 9 + 10 + @Resolver(() => User) 11 + @UseGuards(JwtAuthGuard) 12 + export class UserFieldResolver { 13 + constructor( 14 + private readonly userJobExperienceService: UserJobExperienceService, 15 + private readonly paginationService: PaginationService, 16 + ) {} 17 + 18 + @ResolveField(() => UserJobExperienceConnection, { nullable: true }) 19 + async experience( 20 + @Parent() user: User, 21 + @Args() args: PaginationArgs = {}, 22 + ): Promise<UserJobExperienceConnection> { 23 + const options = this.paginationService.parsePaginationArgs(args); 24 + const result = await this.userJobExperienceService.findManyForUser( 25 + user.id, 26 + options, 27 + ); 28 + return UserJobExperienceConnection.fromPaginationResult(result); 29 + } 30 + }
+46
apps/server/src/modules/job-experience/employment/graphql/user-job-experience-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import type { UserJobExperience as UserJobExperienceEntity } from "../user-job-experience.entity"; 4 + import { UserJobExperience } from "./user-job-experience.type"; 5 + import { UserJobExperienceEdge } from "./user-job-experience-edge.type"; 6 + 7 + @ObjectType() 8 + export class UserJobExperienceConnection { 9 + @Field(() => [UserJobExperienceEdge]) 10 + edges: UserJobExperienceEdge[]; 11 + 12 + @Field(() => PageInfo) 13 + pageInfo: PageInfo; 14 + 15 + @Field(() => Int) 16 + totalCount: number; 17 + 18 + constructor( 19 + edges: UserJobExperienceEdge[], 20 + pageInfo: PageInfo, 21 + totalCount: number, 22 + ) { 23 + this.edges = edges; 24 + this.pageInfo = pageInfo; 25 + this.totalCount = totalCount; 26 + } 27 + 28 + /** 29 + * Static factory method to create a connection from pagination result 30 + */ 31 + static fromPaginationResult( 32 + result: PaginationResult<UserJobExperienceEntity>, 33 + ): UserJobExperienceConnection { 34 + const edges = result.edges.map((edge) => 35 + UserJobExperienceEdge.fromPaginationEdge({ 36 + cursor: edge.cursor, 37 + node: UserJobExperience.fromDomain(edge.node), 38 + }), 39 + ); 40 + return new UserJobExperienceConnection( 41 + edges, 42 + result.pageInfo, 43 + result.totalCount, 44 + ); 45 + } 46 + }
+13
apps/server/src/modules/job-experience/employment/graphql/user-job-experience-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 { UserJobExperience } from "./user-job-experience.type"; 5 + 6 + @ObjectType() 7 + export class UserJobExperienceEdge extends BaseEdge<UserJobExperience> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => UserJobExperience) 12 + declare node: UserJobExperience; 13 + }
+89
apps/server/src/modules/job-experience/employment/graphql/user-job-experience.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import { Company } from "@/modules/job-experience/company/graphql/company.type"; 3 + import type { UserJobExperience as DomainUserJobExperience } from "@/modules/job-experience/employment/user-job-experience.entity"; 4 + import { Level } from "@/modules/job-experience/level/graphql/level.type"; 5 + import { Role } from "@/modules/job-experience/role/graphql/role.type"; 6 + import { Skill } from "@/modules/job-experience/skill/graphql/skill.type"; 7 + import type { Skill as SkillDomain } from "@/modules/job-experience/skill/skill.entity"; 8 + 9 + @ObjectType() 10 + export class UserJobExperience { 11 + @Field(() => ID) 12 + id: string; 13 + 14 + @Field(() => Company) 15 + company: Company; 16 + 17 + @Field(() => Role) 18 + role: Role; 19 + 20 + @Field(() => Level) 21 + level: Level; 22 + 23 + @Field(() => [Skill]) 24 + skills: Skill[]; 25 + 26 + @Field() 27 + startDate: Date; 28 + 29 + @Field({ nullable: true }) 30 + endDate?: Date; 31 + 32 + @Field({ nullable: true }) 33 + description?: string; 34 + 35 + @Field() 36 + createdAt: Date; 37 + 38 + @Field() 39 + updatedAt: Date; 40 + 41 + constructor( 42 + id: string, 43 + company: Company, 44 + role: Role, 45 + level: Level, 46 + skills: Skill[], 47 + startDate: Date, 48 + createdAt: Date, 49 + updatedAt: Date, 50 + endDate?: Date, 51 + description?: string, 52 + ) { 53 + this.id = id; 54 + this.company = company; 55 + this.role = role; 56 + this.level = level; 57 + this.skills = skills; 58 + this.startDate = startDate; 59 + this.createdAt = createdAt; 60 + this.updatedAt = updatedAt; 61 + if (endDate !== undefined) { 62 + this.endDate = endDate; 63 + } 64 + if (description !== undefined) { 65 + this.description = description; 66 + } 67 + } 68 + 69 + static fromDomain( 70 + domainExperience: DomainUserJobExperience, 71 + ): UserJobExperience { 72 + return new UserJobExperience( 73 + domainExperience.id, 74 + Company.fromDomain(domainExperience.company), 75 + Role.fromDomain(domainExperience.role), 76 + Level.fromDomain(domainExperience.level), 77 + domainExperience.skills 78 + ? domainExperience.skills.map((skill: SkillDomain) => 79 + Skill.fromDomain(skill), 80 + ) 81 + : [], 82 + domainExperience.startDate, 83 + domainExperience.createdAt, 84 + domainExperience.updatedAt, 85 + domainExperience.endDate, 86 + domainExperience.description, 87 + ); 88 + } 89 + }
+9
apps/server/src/modules/job-experience/level/graphql/level-connection-args.type.ts
··· 1 + import { ArgsType, Field } from "@nestjs/graphql"; 2 + import { GraphQLString } from "graphql"; 3 + import { BasePaginationArgs } from "@/modules/base/pagination.types"; 4 + 5 + @ArgsType() 6 + export class LevelConnectionArgs extends BasePaginationArgs { 7 + @Field(() => GraphQLString, { nullable: true }) 8 + searchTerm?: string | null; 9 + }
+38
apps/server/src/modules/job-experience/level/graphql/level-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import { Level as LevelDomain } from "@/modules/job-experience/level/level.entity"; 4 + import { Level } from "./level.type"; 5 + import { LevelEdge } from "./level-edge.type"; 6 + 7 + @ObjectType() 8 + export class LevelConnection { 9 + @Field(() => [LevelEdge]) 10 + edges: LevelEdge[]; 11 + 12 + @Field(() => PageInfo) 13 + pageInfo: PageInfo; 14 + 15 + @Field(() => Int) 16 + totalCount: number; 17 + 18 + constructor(edges: LevelEdge[], pageInfo: PageInfo, totalCount: number) { 19 + this.edges = edges; 20 + this.pageInfo = pageInfo; 21 + this.totalCount = totalCount; 22 + } 23 + 24 + /** 25 + * Static factory method to create a connection from pagination result 26 + */ 27 + static fromPaginationResult( 28 + result: PaginationResult<LevelDomain>, 29 + ): LevelConnection { 30 + const edges = result.edges.map((edge) => 31 + LevelEdge.fromPaginationEdge({ 32 + cursor: edge.cursor, 33 + node: Level.fromDomain(edge.node), 34 + }), 35 + ); 36 + return new LevelConnection(edges, result.pageInfo, result.totalCount); 37 + } 38 + }
+13
apps/server/src/modules/job-experience/level/graphql/level-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 { Level } from "./level.type"; 5 + 6 + @ObjectType() 7 + export class LevelEdge extends BaseEdge<Level> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => Level) 12 + declare node: Level; 13 + }
+77
apps/server/src/modules/job-experience/level/graphql/level.resolver.ts
··· 1 + import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 2 + import { PaginationService } from "@/modules/base/pagination.service"; 3 + import { LevelService } from "@/modules/job-experience/level/level.service"; 4 + import { Level } from "./level.type"; 5 + import { LevelConnection } from "./level-connection.type"; 6 + import { LevelConnectionArgs } from "./level-connection-args.type"; 7 + 8 + @Resolver(() => Level) 9 + export class LevelResolver { 10 + constructor( 11 + private readonly levelService: LevelService, 12 + private readonly paginationService: PaginationService, 13 + ) {} 14 + 15 + @Query(() => LevelConnection) 16 + async levels( 17 + @Args() args: LevelConnectionArgs = {}, 18 + ): Promise<LevelConnection> { 19 + const options = this.paginationService.parsePaginationArgs(args); 20 + const filters = { searchTerm: args.searchTerm || undefined }; 21 + 22 + const [items, totalCount] = await Promise.all([ 23 + this.levelService.findMany(filters), 24 + this.levelService.count(filters), 25 + ]); 26 + 27 + const result = this.paginationService.buildPaginationResult( 28 + items, 29 + totalCount, 30 + options, 31 + ); 32 + 33 + return LevelConnection.fromPaginationResult(result); 34 + } 35 + 36 + @Query(() => Level) 37 + async level(@Args("id") id: string): Promise<Level> { 38 + const domainLevel = await this.levelService.findByIdOrFail(id); 39 + return Level.fromDomain(domainLevel); 40 + } 41 + 42 + @Mutation(() => Level) 43 + async createLevel( 44 + @Args("name") name: string, 45 + @Args("description", { nullable: true }) description?: string, 46 + ): Promise<Level> { 47 + const createData: { name: string; description?: string } = { name }; 48 + if (description !== undefined) { 49 + createData.description = description; 50 + } 51 + const domainLevel = await this.levelService.create(createData); 52 + return Level.fromDomain(domainLevel); 53 + } 54 + 55 + @Mutation(() => Level) 56 + async updateLevel( 57 + @Args("id") id: string, 58 + @Args("name", { nullable: true }) name?: string, 59 + @Args("description", { nullable: true }) description?: string, 60 + ): Promise<Level> { 61 + const updateData: { name?: string; description?: string } = {}; 62 + if (name !== undefined) { 63 + updateData.name = name; 64 + } 65 + if (description !== undefined) { 66 + updateData.description = description; 67 + } 68 + const domainLevel = await this.levelService.update(id, updateData); 69 + return Level.fromDomain(domainLevel); 70 + } 71 + 72 + @Mutation(() => Boolean) 73 + async deleteLevel(@Args("id") id: string): Promise<boolean> { 74 + await this.levelService.delete(id); 75 + return true; 76 + } 77 + }
+46
apps/server/src/modules/job-experience/level/graphql/level.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import type { Level as DomainLevel } from "@/modules/job-experience/level/level.entity"; 3 + 4 + @ObjectType() 5 + export class Level { 6 + @Field(() => ID) 7 + id: string; 8 + 9 + @Field() 10 + name: string; 11 + 12 + @Field({ nullable: true }) 13 + description?: string; 14 + 15 + @Field() 16 + createdAt: Date; 17 + 18 + @Field() 19 + updatedAt: Date; 20 + 21 + constructor( 22 + id: string, 23 + name: string, 24 + createdAt: Date, 25 + updatedAt: Date, 26 + description?: string, 27 + ) { 28 + this.id = id; 29 + this.name = name; 30 + this.createdAt = createdAt; 31 + this.updatedAt = updatedAt; 32 + if (description !== undefined) { 33 + this.description = description; 34 + } 35 + } 36 + 37 + static fromDomain(domainLevel: DomainLevel): Level { 38 + return new Level( 39 + domainLevel.id, 40 + domainLevel.name, 41 + domainLevel.createdAt, 42 + domainLevel.updatedAt, 43 + domainLevel.description, 44 + ); 45 + } 46 + }
+9
apps/server/src/modules/job-experience/role/graphql/role-connection-args.type.ts
··· 1 + import { ArgsType, Field } from "@nestjs/graphql"; 2 + import { GraphQLString } from "graphql"; 3 + import { BasePaginationArgs } from "@/modules/base/pagination.types"; 4 + 5 + @ArgsType() 6 + export class RoleConnectionArgs extends BasePaginationArgs { 7 + @Field(() => GraphQLString, { nullable: true }) 8 + searchTerm?: string | null; 9 + }
+38
apps/server/src/modules/job-experience/role/graphql/role-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import { Role as RoleDomain } from "@/modules/job-experience/role/role.entity"; 4 + import { Role } from "./role.type"; 5 + import { RoleEdge } from "./role-edge.type"; 6 + 7 + @ObjectType() 8 + export class RoleConnection { 9 + @Field(() => [RoleEdge]) 10 + edges: RoleEdge[]; 11 + 12 + @Field(() => PageInfo) 13 + pageInfo: PageInfo; 14 + 15 + @Field(() => Int) 16 + totalCount: number; 17 + 18 + constructor(edges: RoleEdge[], pageInfo: PageInfo, totalCount: number) { 19 + this.edges = edges; 20 + this.pageInfo = pageInfo; 21 + this.totalCount = totalCount; 22 + } 23 + 24 + /** 25 + * Static factory method to create a connection from pagination result 26 + */ 27 + static fromPaginationResult( 28 + result: PaginationResult<RoleDomain>, 29 + ): RoleConnection { 30 + const edges = result.edges.map((edge) => 31 + RoleEdge.fromPaginationEdge({ 32 + cursor: edge.cursor, 33 + node: Role.fromDomain(edge.node), 34 + }), 35 + ); 36 + return new RoleConnection(edges, result.pageInfo, result.totalCount); 37 + } 38 + }
+13
apps/server/src/modules/job-experience/role/graphql/role-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 { Role } from "./role.type"; 5 + 6 + @ObjectType() 7 + export class RoleEdge extends BaseEdge<Role> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => Role) 12 + declare node: Role; 13 + }
+75
apps/server/src/modules/job-experience/role/graphql/role.resolver.ts
··· 1 + import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 2 + import { PaginationService } from "@/modules/base/pagination.service"; 3 + import { RoleService } from "@/modules/job-experience/role/role.service"; 4 + import { Role } from "./role.type"; 5 + import { RoleConnection } from "./role-connection.type"; 6 + import { RoleConnectionArgs } from "./role-connection-args.type"; 7 + 8 + @Resolver(() => Role) 9 + export class RoleResolver { 10 + constructor( 11 + private readonly roleService: RoleService, 12 + private readonly paginationService: PaginationService, 13 + ) {} 14 + 15 + @Query(() => RoleConnection) 16 + async roles(@Args() args: RoleConnectionArgs = {}): Promise<RoleConnection> { 17 + const options = this.paginationService.parsePaginationArgs(args); 18 + const filters = { searchTerm: args.searchTerm || undefined }; 19 + 20 + const [items, totalCount] = await Promise.all([ 21 + this.roleService.findMany(filters), 22 + this.roleService.count(filters), 23 + ]); 24 + 25 + const result = this.paginationService.buildPaginationResult( 26 + items, 27 + totalCount, 28 + options, 29 + ); 30 + 31 + return RoleConnection.fromPaginationResult(result); 32 + } 33 + 34 + @Query(() => Role) 35 + async role(@Args("id") id: string): Promise<Role> { 36 + const domainRole = await this.roleService.findByIdOrFail(id); 37 + return Role.fromDomain(domainRole); 38 + } 39 + 40 + @Mutation(() => Role) 41 + async createRole( 42 + @Args("name") name: string, 43 + @Args("description", { nullable: true }) description?: string, 44 + ): Promise<Role> { 45 + const createData: { name: string; description?: string } = { name }; 46 + if (description !== undefined) { 47 + createData.description = description; 48 + } 49 + const domainRole = await this.roleService.create(createData); 50 + return Role.fromDomain(domainRole); 51 + } 52 + 53 + @Mutation(() => Role) 54 + async updateRole( 55 + @Args("id") id: string, 56 + @Args("name", { nullable: true }) name?: string, 57 + @Args("description", { nullable: true }) description?: string, 58 + ): Promise<Role> { 59 + const updateData: { name?: string; description?: string } = {}; 60 + if (name !== undefined) { 61 + updateData.name = name; 62 + } 63 + if (description !== undefined) { 64 + updateData.description = description; 65 + } 66 + const domainRole = await this.roleService.update(id, updateData); 67 + return Role.fromDomain(domainRole); 68 + } 69 + 70 + @Mutation(() => Boolean) 71 + async deleteRole(@Args("id") id: string): Promise<boolean> { 72 + await this.roleService.delete(id); 73 + return true; 74 + } 75 + }
+46
apps/server/src/modules/job-experience/role/graphql/role.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import type { Role as DomainRole } from "@/modules/job-experience/role/role.entity"; 3 + 4 + @ObjectType() 5 + export class Role { 6 + @Field(() => ID) 7 + id: string; 8 + 9 + @Field() 10 + name: string; 11 + 12 + @Field({ nullable: true }) 13 + description?: string; 14 + 15 + @Field() 16 + createdAt: Date; 17 + 18 + @Field() 19 + updatedAt: Date; 20 + 21 + constructor( 22 + id: string, 23 + name: string, 24 + createdAt: Date, 25 + updatedAt: Date, 26 + description?: string, 27 + ) { 28 + this.id = id; 29 + this.name = name; 30 + this.createdAt = createdAt; 31 + this.updatedAt = updatedAt; 32 + if (description !== undefined) { 33 + this.description = description; 34 + } 35 + } 36 + 37 + static fromDomain(domainRole: DomainRole): Role { 38 + return new Role( 39 + domainRole.id, 40 + domainRole.name, 41 + domainRole.createdAt, 42 + domainRole.updatedAt, 43 + domainRole.description, 44 + ); 45 + } 46 + }
+19
apps/server/src/modules/job-experience/skill/graphql/skill-connection-args.type.ts
··· 1 + import { ArgsType, Field, Int } from "@nestjs/graphql"; 2 + 3 + @ArgsType() 4 + export class SkillConnectionArgs { 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 + 17 + @Field(() => String, { nullable: true }) 18 + searchTerm?: string | null; 19 + }
+38
apps/server/src/modules/job-experience/skill/graphql/skill-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import { Skill as SkillDomain } from "@/modules/job-experience/skill/skill.entity"; 4 + import { Skill } from "./skill.type"; 5 + import { SkillEdge } from "./skill-edge.type"; 6 + 7 + @ObjectType() 8 + export class SkillConnection { 9 + @Field(() => [SkillEdge]) 10 + edges: SkillEdge[]; 11 + 12 + @Field(() => PageInfo) 13 + pageInfo: PageInfo; 14 + 15 + @Field(() => Int) 16 + totalCount: number; 17 + 18 + constructor(edges: SkillEdge[], pageInfo: PageInfo, totalCount: number) { 19 + this.edges = edges; 20 + this.pageInfo = pageInfo; 21 + this.totalCount = totalCount; 22 + } 23 + 24 + /** 25 + * Static factory method to create a connection from pagination result 26 + */ 27 + static fromPaginationResult( 28 + result: PaginationResult<SkillDomain>, 29 + ): SkillConnection { 30 + const edges = result.edges.map((edge) => 31 + SkillEdge.fromPaginationEdge({ 32 + cursor: edge.cursor, 33 + node: Skill.fromDomain(edge.node), 34 + }), 35 + ); 36 + return new SkillConnection(edges, result.pageInfo, result.totalCount); 37 + } 38 + }
+13
apps/server/src/modules/job-experience/skill/graphql/skill-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 { Skill } from "./skill.type"; 5 + 6 + @ObjectType() 7 + export class SkillEdge extends BaseEdge<Skill> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => Skill) 12 + declare node: Skill; 13 + }
+77
apps/server/src/modules/job-experience/skill/graphql/skill.resolver.ts
··· 1 + import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 2 + import { PaginationService } from "@/modules/base/pagination.service"; 3 + import { SkillService } from "@/modules/job-experience/skill/skill.service"; 4 + import { Skill } from "./skill.type"; 5 + import { SkillConnection } from "./skill-connection.type"; 6 + import { SkillConnectionArgs } from "./skill-connection-args.type"; 7 + 8 + @Resolver(() => Skill) 9 + export class SkillResolver { 10 + constructor( 11 + private readonly skillService: SkillService, 12 + private readonly paginationService: PaginationService, 13 + ) {} 14 + 15 + @Query(() => SkillConnection) 16 + async skills( 17 + @Args() args: SkillConnectionArgs = {}, 18 + ): Promise<SkillConnection> { 19 + const options = this.paginationService.parsePaginationArgs(args); 20 + const filters = { searchTerm: args.searchTerm || undefined }; 21 + 22 + const [items, totalCount] = await Promise.all([ 23 + this.skillService.findMany(filters), 24 + this.skillService.count(filters), 25 + ]); 26 + 27 + const result = this.paginationService.buildPaginationResult( 28 + items, 29 + totalCount, 30 + options, 31 + ); 32 + 33 + return SkillConnection.fromPaginationResult(result); 34 + } 35 + 36 + @Query(() => Skill) 37 + async skill(@Args("id") id: string): Promise<Skill> { 38 + const domainSkill = await this.skillService.findByIdOrFail(id); 39 + return Skill.fromDomain(domainSkill); 40 + } 41 + 42 + @Mutation(() => Skill) 43 + async createSkill( 44 + @Args("name") name: string, 45 + @Args("description", { nullable: true }) description?: string, 46 + ): Promise<Skill> { 47 + const createData: { name: string; description?: string } = { name }; 48 + if (description !== undefined) { 49 + createData.description = description; 50 + } 51 + const domainSkill = await this.skillService.create(createData); 52 + return Skill.fromDomain(domainSkill); 53 + } 54 + 55 + @Mutation(() => Skill) 56 + async updateSkill( 57 + @Args("id") id: string, 58 + @Args("name", { nullable: true }) name?: string, 59 + @Args("description", { nullable: true }) description?: string, 60 + ): Promise<Skill> { 61 + const updateData: { name?: string; description?: string } = {}; 62 + if (name !== undefined) { 63 + updateData.name = name; 64 + } 65 + if (description !== undefined) { 66 + updateData.description = description; 67 + } 68 + const domainSkill = await this.skillService.update(id, updateData); 69 + return Skill.fromDomain(domainSkill); 70 + } 71 + 72 + @Mutation(() => Boolean) 73 + async deleteSkill(@Args("id") id: string): Promise<boolean> { 74 + await this.skillService.delete(id); 75 + return true; 76 + } 77 + }
+46
apps/server/src/modules/job-experience/skill/graphql/skill.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import type { Skill as DomainSkill } from "@/modules/job-experience/skill/skill.entity"; 3 + 4 + @ObjectType() 5 + export class Skill { 6 + @Field(() => ID) 7 + id: string; 8 + 9 + @Field() 10 + name: string; 11 + 12 + @Field({ nullable: true }) 13 + description?: string; 14 + 15 + @Field() 16 + createdAt: Date; 17 + 18 + @Field() 19 + updatedAt: Date; 20 + 21 + constructor( 22 + id: string, 23 + name: string, 24 + createdAt: Date, 25 + updatedAt: Date, 26 + description?: string, 27 + ) { 28 + this.id = id; 29 + this.name = name; 30 + this.createdAt = createdAt; 31 + this.updatedAt = updatedAt; 32 + if (description !== undefined) { 33 + this.description = description; 34 + } 35 + } 36 + 37 + static fromDomain(domainSkill: DomainSkill): Skill { 38 + return new Skill( 39 + domainSkill.id, 40 + domainSkill.name, 41 + domainSkill.createdAt, 42 + domainSkill.updatedAt, 43 + domainSkill.description, 44 + ); 45 + } 46 + }
+25
apps/server/src/modules/organization/graphql/membership-connection-args.type.ts
··· 1 + import { ArgsType, Field, Int } from "@nestjs/graphql"; 2 + 3 + @ArgsType() 4 + export class MembershipConnectionArgs { 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 + 17 + @Field(() => String, { nullable: true }) 18 + searchTerm?: string | null; 19 + 20 + @Field(() => String, { nullable: true, defaultValue: "createdAt" }) 21 + sortBy?: string | null; 22 + 23 + @Field(() => String, { nullable: true, defaultValue: "asc" }) 24 + sortOrder?: string | null; 25 + }
+59
apps/server/src/modules/organization/graphql/membership-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import { Membership } from "./membership.type"; 4 + import { MembershipEdge } from "./membership-edge.type"; 5 + 6 + @ObjectType() 7 + export class MembershipConnection { 8 + @Field(() => [MembershipEdge]) 9 + edges: MembershipEdge[]; 10 + 11 + @Field(() => PageInfo) 12 + pageInfo: PageInfo; 13 + 14 + @Field(() => Int) 15 + totalCount: number; 16 + 17 + constructor(edges: MembershipEdge[], 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<{ 28 + id: string; 29 + createdAt: Date; 30 + user: { 31 + id: string; 32 + name: string; 33 + email: string; 34 + createdAt: Date; 35 + }; 36 + role: { 37 + id: string; 38 + name: string; 39 + description: string | null; 40 + color: string | null; 41 + createdAt: Date; 42 + updatedAt: Date; 43 + }; 44 + }>, 45 + ): MembershipConnection { 46 + const edges = result.edges.map((edge) => 47 + MembershipEdge.fromPaginationEdge({ 48 + cursor: edge.cursor, 49 + node: Membership.fromDomain({ 50 + id: edge.node.id, 51 + joinedAt: edge.node.createdAt, 52 + user: edge.node.user, 53 + role: edge.node.role, 54 + }), 55 + }), 56 + ); 57 + return new MembershipConnection(edges, result.pageInfo, result.totalCount); 58 + } 59 + }
+13
apps/server/src/modules/organization/graphql/membership-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 { Membership } from "./membership.type"; 5 + 6 + @ObjectType() 7 + export class MembershipEdge extends BaseEdge<Membership> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => Membership) 12 + declare node: Membership; 13 + }
+44
apps/server/src/modules/organization/graphql/membership.type.ts
··· 1 + import { Field, ObjectType } from "@nestjs/graphql"; 2 + import { OrganizationRole } from "@/modules/organization/organization-role.entity"; 3 + import { User } from "@/modules/user/user.type"; 4 + 5 + @ObjectType() 6 + export class Membership { 7 + @Field(() => String) 8 + id: string; 9 + 10 + @Field(() => Date) 11 + joinedAt: Date; 12 + 13 + @Field(() => User) 14 + user: User; 15 + 16 + @Field(() => OrganizationRole) 17 + role: OrganizationRole; 18 + 19 + constructor(data: { 20 + id: string; 21 + joinedAt: Date; 22 + user: User; 23 + role: OrganizationRole; 24 + }) { 25 + this.id = data.id; 26 + this.joinedAt = data.joinedAt; 27 + this.user = data.user; 28 + this.role = data.role; 29 + } 30 + 31 + static fromDomain(domainMembership: { 32 + id: string; 33 + joinedAt: Date; 34 + user: User; 35 + role: OrganizationRole; 36 + }): Membership { 37 + return new Membership({ 38 + id: domainMembership.id, 39 + joinedAt: domainMembership.joinedAt, 40 + user: domainMembership.user, 41 + role: domainMembership.role, 42 + }); 43 + } 44 + }
+129
apps/server/src/modules/organization/graphql/organization.resolver.ts
··· 1 + import { UseGuards } from "@nestjs/common"; 2 + import { 3 + Args, 4 + Mutation, 5 + Parent, 6 + Query, 7 + ResolveField, 8 + Resolver, 9 + } from "@nestjs/graphql"; 10 + import { JwtAuthGuard } from "@/modules/auth/jwt-auth.guard"; 11 + import { PaginationService } from "@/modules/base/pagination.service"; 12 + import { Organization } from "@/modules/organization/organization.entity"; 13 + import { 14 + type MembershipSortField, 15 + type MembershipSortOrder, 16 + OrganizationService, 17 + } from "@/modules/organization/organization.service"; 18 + import { MembershipConnection } from "./membership-connection.type"; 19 + import { MembershipConnectionArgs } from "./membership-connection-args.type"; 20 + 21 + @Resolver(() => Organization) 22 + @UseGuards(JwtAuthGuard) 23 + export class OrganizationResolver { 24 + constructor( 25 + private readonly organizationService: OrganizationService, 26 + private readonly paginationService: PaginationService, 27 + ) {} 28 + 29 + @Query(() => Organization, { name: "organization", nullable: true }) 30 + async getOrganization(@Args("id") id: string): Promise<Organization | null> { 31 + return this.organizationService.findById(id); 32 + } 33 + 34 + @ResolveField(() => MembershipConnection) 35 + async memberships( 36 + @Parent() organization: Organization, 37 + @Args() args: MembershipConnectionArgs, 38 + ): Promise<MembershipConnection> { 39 + const paginationOptions = this.paginationService.parsePaginationArgs(args); 40 + 41 + const sortBy = (args.sortBy || "createdAt") as MembershipSortField; 42 + const sortOrder = (args.sortOrder || "asc") as MembershipSortOrder; 43 + const searchTerm = args.searchTerm; 44 + 45 + const [items, totalCount] = await Promise.all([ 46 + this.organizationService.findMembershipsByOrganization( 47 + organization.id, 48 + sortBy, 49 + sortOrder, 50 + searchTerm ?? undefined, 51 + ), 52 + this.organizationService.countMembershipsByOrganization( 53 + organization.id, 54 + searchTerm ?? undefined, 55 + ), 56 + ]); 57 + 58 + const result = this.paginationService.buildPaginationResult( 59 + items, 60 + totalCount, 61 + paginationOptions, 62 + ); 63 + 64 + return MembershipConnection.fromPaginationResult(result); 65 + } 66 + 67 + @ResolveField(() => Number) 68 + async memberCount(@Parent() organization: Organization): Promise<number> { 69 + return this.organizationService.countMembershipsByOrganization( 70 + organization.id, 71 + ); 72 + } 73 + 74 + @Mutation(() => Organization) 75 + async createOrganization( 76 + @Args("name") name: string, 77 + @Args("description", { nullable: true }) description?: string, 78 + ): Promise<Organization> { 79 + const data: { name: string; description?: string } = { name }; 80 + if (description !== undefined) { 81 + data.description = description; 82 + } 83 + return this.organizationService.create(data); 84 + } 85 + 86 + @Mutation(() => Organization) 87 + async updateOrganization( 88 + @Args("id") id: string, 89 + @Args("name", { nullable: true }) name?: string, 90 + @Args("description", { nullable: true }) description?: string, 91 + ): Promise<Organization> { 92 + const data: { name?: string; description?: string } = {}; 93 + if (name !== undefined) { 94 + data.name = name; 95 + } 96 + if (description !== undefined) { 97 + data.description = description; 98 + } 99 + return this.organizationService.update(id, data); 100 + } 101 + 102 + @Mutation(() => Boolean) 103 + async deleteOrganization(@Args("id") id: string): Promise<boolean> { 104 + await this.organizationService.delete(id); 105 + return true; 106 + } 107 + 108 + @Mutation(() => Boolean) 109 + async addUserToOrganization( 110 + @Args("organizationId") organizationId: string, 111 + @Args("userId") userId: string, 112 + ): Promise<boolean> { 113 + return this.organizationService.addUserToOrganization( 114 + organizationId, 115 + userId, 116 + ); 117 + } 118 + 119 + @Mutation(() => Boolean) 120 + async removeUserFromOrganization( 121 + @Args("organizationId") organizationId: string, 122 + @Args("userId") userId: string, 123 + ): Promise<boolean> { 124 + return this.organizationService.removeUserFromOrganization( 125 + organizationId, 126 + userId, 127 + ); 128 + } 129 + }
+14
apps/server/src/modules/organization/graphql/user-field.resolver.ts
··· 1 + import { Parent, ResolveField, Resolver } from "@nestjs/graphql"; 2 + import { Organization } from "@/modules/organization/organization.entity"; 3 + import { OrganizationService } from "@/modules/organization/organization.service"; 4 + import { User } from "@/modules/user/user.type"; 5 + 6 + @Resolver(() => User) 7 + export class UserFieldResolver { 8 + constructor(private readonly organizationService: OrganizationService) {} 9 + 10 + @ResolveField(() => [Organization]) 11 + async organizations(@Parent() user: User): Promise<Organization[]> { 12 + return this.organizationService.findForUser(user.id); 13 + } 14 + }
+33
apps/server/src/modules/vacancies/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 { PaginationArgs } from "@/modules/base/pagination.types"; 6 + import { User } from "@/modules/user/user.type"; 7 + import { VacancyService } from "../vacancy.service"; 8 + import { VacancyConnection } from "./vacancy-connection.type"; 9 + import { VacancyFilterInput } from "./vacancy-filter.input"; 10 + 11 + @Resolver(() => User) 12 + @UseGuards(JwtAuthGuard) 13 + export class VacancyUserFieldResolver { 14 + constructor( 15 + private readonly vacancyService: VacancyService, 16 + private readonly paginationService: PaginationService, 17 + ) {} 18 + 19 + @ResolveField(() => VacancyConnection, { nullable: true }) 20 + async vacancies( 21 + @Parent() user: User, 22 + @Args() paginationArgs: PaginationArgs = {}, 23 + @Args("filter", { nullable: true }) filter?: VacancyFilterInput, 24 + ): Promise<VacancyConnection> { 25 + const options = this.paginationService.parsePaginationArgs(paginationArgs); 26 + const result = await this.vacancyService.findManyForUserWithFilters( 27 + user.id, 28 + filter, 29 + options, 30 + ); 31 + return VacancyConnection.fromPaginationResult(result); 32 + } 33 + }
+38
apps/server/src/modules/vacancies/graphql/vacancy-connection.type.ts
··· 1 + import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 + import { PageInfo, PaginationResult } from "@/modules/base/pagination.types"; 3 + import type { Vacancy as VacancyEntity } from "../vacancy.entity"; 4 + import { Vacancy } from "./vacancy.type"; 5 + import { VacancyEdge } from "./vacancy-edge.type"; 6 + 7 + @ObjectType() 8 + export class VacancyConnection { 9 + @Field(() => [VacancyEdge]) 10 + edges: VacancyEdge[]; 11 + 12 + @Field(() => PageInfo) 13 + pageInfo: PageInfo; 14 + 15 + @Field(() => Int) 16 + totalCount: number; 17 + 18 + constructor(edges: VacancyEdge[], pageInfo: PageInfo, totalCount: number) { 19 + this.edges = edges; 20 + this.pageInfo = pageInfo; 21 + this.totalCount = totalCount; 22 + } 23 + 24 + /** 25 + * Static factory method to create a connection from pagination result 26 + */ 27 + static fromPaginationResult( 28 + result: PaginationResult<VacancyEntity>, 29 + ): VacancyConnection { 30 + const edges = result.edges.map((edge) => 31 + VacancyEdge.fromPaginationEdge({ 32 + cursor: edge.cursor, 33 + node: Vacancy.fromDomain(edge.node), 34 + }), 35 + ); 36 + return new VacancyConnection(edges, result.pageInfo, result.totalCount); 37 + } 38 + }
+13
apps/server/src/modules/vacancies/graphql/vacancy-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 { Vacancy } from "./vacancy.type"; 5 + 6 + @ObjectType() 7 + export class VacancyEdge extends BaseEdge<Vacancy> { 8 + @Field(() => GraphQLString) 9 + declare cursor: string; 10 + 11 + @Field(() => Vacancy) 12 + declare node: Vacancy; 13 + }
+28
apps/server/src/modules/vacancies/graphql/vacancy-filter.input.ts
··· 1 + import { Field, InputType, Int } from "@nestjs/graphql"; 2 + 3 + @InputType() 4 + export class VacancyFilterInput { 5 + @Field(() => String, { nullable: true }) 6 + searchTerm?: string | null; 7 + 8 + @Field(() => [String], { nullable: true }) 9 + jobTypes?: string[] | null; 10 + 11 + @Field(() => [String], { nullable: true }) 12 + levels?: string[] | null; 13 + 14 + @Field(() => [String], { nullable: true }) 15 + companies?: string[] | null; 16 + 17 + @Field(() => [String], { nullable: true }) 18 + locations?: string[] | null; 19 + 20 + @Field(() => Int, { nullable: true }) 21 + minSalary?: number | null; 22 + 23 + @Field(() => Int, { nullable: true }) 24 + maxSalary?: number | null; 25 + 26 + @Field(() => Boolean, { nullable: true }) 27 + isPublic?: boolean | null; 28 + }
+131
apps/server/src/modules/vacancies/graphql/vacancy.resolver.ts
··· 1 + import { UseGuards } from "@nestjs/common"; 2 + import { 3 + Args, 4 + Mutation, 5 + Parent, 6 + Query, 7 + ResolveField, 8 + Resolver, 9 + } from "@nestjs/graphql"; 10 + import { CurrentUser } from "@/modules/auth/current-user.decorator"; 11 + import { JwtAuthGuard } from "@/modules/auth/jwt-auth.guard"; 12 + import { CompanyService } from "@/modules/job-experience/company/company.service"; 13 + import { Company } from "@/modules/job-experience/company/graphql/company.type"; 14 + import { Level } from "@/modules/job-experience/level/graphql/level.type"; 15 + import { LevelService } from "@/modules/job-experience/level/level.service"; 16 + import { Role } from "@/modules/job-experience/role/graphql/role.type"; 17 + import { RoleService } from "@/modules/job-experience/role/role.service"; 18 + import { Skill } from "@/modules/job-experience/skill/graphql/skill.type"; 19 + import { SkillService } from "@/modules/job-experience/skill/skill.service"; 20 + import { User } from "@/modules/user/user.type"; 21 + import type { Vacancy as VacancyEntity } from "@/modules/vacancies/vacancy.entity"; 22 + import { VacancyService } from "@/modules/vacancies/vacancy.service"; 23 + import { Vacancy } from "./vacancy.type"; 24 + import { VacancyFilterInput } from "./vacancy-filter.input"; 25 + 26 + @Resolver(() => Vacancy) 27 + @UseGuards(JwtAuthGuard) 28 + export class VacancyResolver { 29 + constructor( 30 + private readonly vacancyService: VacancyService, 31 + private readonly companyService: CompanyService, 32 + private readonly roleService: RoleService, 33 + private readonly levelService: LevelService, 34 + private readonly skillService: SkillService, 35 + ) {} 36 + 37 + @Query(() => [Vacancy]) 38 + async myVacancies( 39 + @CurrentUser() user: User, 40 + @Args("filter", { nullable: true }) filter?: VacancyFilterInput, 41 + ): Promise<Vacancy[]> { 42 + const domainVacancies = await this.vacancyService.findForUserWithFilters( 43 + user.id, 44 + filter, 45 + ); 46 + return domainVacancies.map((vacancy: VacancyEntity) => 47 + Vacancy.fromDomain(vacancy), 48 + ); 49 + } 50 + 51 + @Mutation(() => Vacancy) 52 + async createVacancy( 53 + @CurrentUser() user: User, 54 + @Args("title") title: string, 55 + @Args("companyId") companyId: string, 56 + @Args("roleId") roleId: string, 57 + @Args("levelId", { nullable: true }) levelId?: string, 58 + @Args("jobTypeId", { nullable: true }) jobTypeId?: string, 59 + @Args("description", { nullable: true }) description?: string, 60 + @Args("requirements", { nullable: true }) requirements?: string, 61 + @Args("location", { nullable: true }) location?: string, 62 + @Args("minSalary", { nullable: true }) minSalary?: number, 63 + @Args("maxSalary", { nullable: true }) maxSalary?: number, 64 + @Args("applicationUrl", { nullable: true }) applicationUrl?: string, 65 + @Args("deadline", { nullable: true }) deadline?: Date, 66 + @Args("isActive", { nullable: true }) isActive?: boolean, 67 + @Args("isPublic", { nullable: true }) isPublic?: boolean, 68 + ): Promise<Vacancy> { 69 + const createData = { 70 + title, 71 + companyId, 72 + roleId, 73 + ...(levelId !== undefined && { levelId }), 74 + ...(jobTypeId !== undefined && { jobTypeId }), 75 + ...(description !== undefined && { description }), 76 + ...(requirements !== undefined && { requirements }), 77 + ...(location !== undefined && { location }), 78 + ...(minSalary !== undefined && { minSalary }), 79 + ...(maxSalary !== undefined && { maxSalary }), 80 + ...(applicationUrl !== undefined && { applicationUrl }), 81 + ...(deadline !== undefined && { deadline }), 82 + ...(isActive !== undefined && { isActive }), 83 + ...(isPublic !== undefined && { isPublic }), 84 + }; 85 + 86 + const domainVacancy = await this.vacancyService.create(user.id, createData); 87 + return Vacancy.fromDomain(domainVacancy); 88 + } 89 + 90 + @Mutation(() => Boolean) 91 + async deleteVacancy( 92 + @CurrentUser() user: User, 93 + @Args("id") id: string, 94 + ): Promise<boolean> { 95 + await this.vacancyService.delete(id, user.id); 96 + return true; 97 + } 98 + 99 + @ResolveField(() => Company, { nullable: true }) 100 + async company(@Parent() vacancy: Vacancy) { 101 + if (!vacancy.companyId) { 102 + return null; 103 + } 104 + const company = await this.companyService.findById(vacancy.companyId); 105 + return company ? Company.fromDomain(company) : null; 106 + } 107 + 108 + @ResolveField(() => Role, { nullable: true }) 109 + async role(@Parent() vacancy: Vacancy) { 110 + if (!vacancy.roleId) { 111 + return null; 112 + } 113 + const role = await this.roleService.findById(vacancy.roleId); 114 + return role ? Role.fromDomain(role) : null; 115 + } 116 + 117 + @ResolveField(() => Level, { nullable: true }) 118 + async level(@Parent() vacancy: Vacancy) { 119 + if (!vacancy.levelId) { 120 + return null; 121 + } 122 + const level = await this.levelService.findById(vacancy.levelId); 123 + return level ? Level.fromDomain(level) : null; 124 + } 125 + 126 + @ResolveField(() => [Skill], { nullable: true }) 127 + async skills(@Parent() vacancy: Vacancy) { 128 + const skills = await this.skillService.findByVacancyId(vacancy.id); 129 + return skills.map((skill) => Skill.fromDomain(skill)); 130 + } 131 + }
+122
apps/server/src/modules/vacancies/graphql/vacancy.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import type { Vacancy as DomainVacancy } from "@/modules/vacancies/vacancy.entity"; 3 + 4 + @ObjectType() 5 + export class Vacancy { 6 + @Field(() => ID) 7 + id: string; 8 + 9 + @Field(() => String) 10 + title: string; 11 + 12 + @Field(() => String) 13 + ownerId: string; 14 + 15 + @Field(() => String) 16 + companyId: string; 17 + 18 + @Field(() => String) 19 + roleId: string; 20 + 21 + @Field(() => String, { nullable: true }) 22 + levelId: string | null; 23 + 24 + @Field(() => String, { nullable: true }) 25 + jobTypeId: string | null; 26 + 27 + @Field(() => String, { nullable: true }) 28 + description: string | null; 29 + 30 + @Field(() => String, { nullable: true }) 31 + requirements: string | null; 32 + 33 + @Field(() => String, { nullable: true }) 34 + location: string | null; 35 + 36 + @Field(() => Number, { nullable: true }) 37 + minSalary: number | null; 38 + 39 + @Field(() => Number, { nullable: true }) 40 + maxSalary: number | null; 41 + 42 + @Field(() => String, { nullable: true }) 43 + applicationUrl: string | null; 44 + 45 + @Field(() => Date, { nullable: true }) 46 + deadline: Date | null; 47 + 48 + @Field(() => Boolean) 49 + isActive: boolean; 50 + 51 + @Field(() => Boolean) 52 + isPublic: boolean; 53 + 54 + @Field(() => Date) 55 + createdAt: Date; 56 + 57 + @Field(() => Date) 58 + updatedAt: Date; 59 + 60 + constructor(data: { 61 + id: string; 62 + title: string; 63 + ownerId: string; 64 + companyId: string; 65 + roleId: string; 66 + levelId?: string | null; 67 + jobTypeId?: string | null; 68 + description?: string | null; 69 + requirements?: string | null; 70 + location?: string | null; 71 + minSalary?: number | null; 72 + maxSalary?: number | null; 73 + applicationUrl?: string | null; 74 + deadline?: Date | null; 75 + isActive: boolean; 76 + isPublic: boolean; 77 + createdAt: Date; 78 + updatedAt: Date; 79 + }) { 80 + this.id = data.id; 81 + this.title = data.title; 82 + this.ownerId = data.ownerId; 83 + this.companyId = data.companyId; 84 + this.roleId = data.roleId; 85 + this.levelId = data.levelId ?? null; 86 + this.jobTypeId = data.jobTypeId ?? null; 87 + this.description = data.description ?? null; 88 + this.requirements = data.requirements ?? null; 89 + this.location = data.location ?? null; 90 + this.minSalary = data.minSalary ?? null; 91 + this.maxSalary = data.maxSalary ?? null; 92 + this.applicationUrl = data.applicationUrl ?? null; 93 + this.deadline = data.deadline ?? null; 94 + this.isActive = data.isActive; 95 + this.isPublic = data.isPublic; 96 + this.createdAt = data.createdAt; 97 + this.updatedAt = data.updatedAt; 98 + } 99 + 100 + static fromDomain(domainVacancy: DomainVacancy): Vacancy { 101 + return new Vacancy({ 102 + id: domainVacancy.id, 103 + title: domainVacancy.title, 104 + ownerId: domainVacancy.ownerId, 105 + companyId: domainVacancy.companyId, 106 + roleId: domainVacancy.roleId, 107 + levelId: domainVacancy.levelId ?? null, 108 + jobTypeId: domainVacancy.jobTypeId ?? null, 109 + description: domainVacancy.description ?? null, 110 + requirements: domainVacancy.requirements ?? null, 111 + location: domainVacancy.location ?? null, 112 + minSalary: domainVacancy.minSalary ?? null, 113 + maxSalary: domainVacancy.maxSalary ?? null, 114 + applicationUrl: domainVacancy.applicationUrl ?? null, 115 + deadline: domainVacancy.deadline ?? null, 116 + isActive: domainVacancy.isActive, 117 + isPublic: domainVacancy.isPublic, 118 + createdAt: domainVacancy.createdAt, 119 + updatedAt: domainVacancy.updatedAt, 120 + }); 121 + } 122 + }