diff --git a/src/components/user/known-language.repository.ts b/src/components/user/known-language.repository.ts new file mode 100644 index 0000000000..35e5a34efa --- /dev/null +++ b/src/components/user/known-language.repository.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@nestjs/common'; +import { node, relation } from 'cypher-query-builder'; +import { DateTime } from 'luxon'; +import { ID } from '~/common'; +import { DtoRepository } from '~/core'; +import { ACTIVE } from '~/core/database/query'; +import { KnownLanguage, ModifyKnownLanguageArgs } from './dto'; + +@Injectable() +export class KnownLanguageRepository extends DtoRepository(KnownLanguage) { + async create({ + userId, + languageId, + languageProficiency, + }: ModifyKnownLanguageArgs) { + await this.delete({ userId, languageId, languageProficiency }); + + await this.db + .query() + .matchNode('user', 'User', { id: userId }) + .matchNode('language', 'Language', { id: languageId }) + .create([ + node('user'), + relation('out', '', 'knownLanguage', { + active: true, + createdAt: DateTime.local(), + value: languageProficiency, + }), + node('language'), + ]) + .run(); + } + + async delete({ + userId, + languageId, + languageProficiency, + }: ModifyKnownLanguageArgs) { + await this.db + .query() + .matchNode('user', 'User', { id: userId }) + .matchNode('language', 'Language', { id: languageId }) + .match([ + [ + node('user'), + relation('out', 'rel', 'knownLanguage', { + active: true, + value: languageProficiency, + }), + node('language'), + ], + ]) + .setValues({ + 'rel.active': false, + }) + .run(); + } + + async list(userId: ID) { + const results = await this.db + .query() + .match([ + node('node', 'Language'), + relation('in', 'knownLanguageRel', 'knownLanguage', ACTIVE), + node('user', 'User', { id: userId }), + ]) + .with('collect(distinct user) as users, node, knownLanguageRel') + .raw(`unwind users as user`) + .return([ + 'knownLanguageRel.value as proficiency', + 'node.id as language', + ]) + .run(); + return results; + } +} diff --git a/src/components/user/user.edgedb.repository.ts b/src/components/user/user.edgedb.repository.ts index a6665562d3..4d5acf1796 100644 --- a/src/components/user/user.edgedb.repository.ts +++ b/src/components/user/user.edgedb.repository.ts @@ -3,7 +3,6 @@ import { DuplicateException, ID, NotFoundException, - Role, ServerException, Session, } from '~/common'; @@ -99,36 +98,6 @@ export class UserEdgedbRepository extends UserRepository { } } - async updateEmail( - user: User, - email: string | null | undefined, - ): Promise { - const query = e.update(e.User, () => ({ - filter_single: { id: user.id }, - set: { email }, - })); - try { - await this.edgedb.run(query); - } catch (e) { - if (isExclusivityViolation(e, 'email')) { - throw new DuplicateException( - 'person.email', - 'Email address is already in use', - e, - ); - } - throw e; - } - } - - async updateRoles(user: User, roles: Role[]): Promise { - const query = e.update(e.User, () => ({ - filter_single: { id: user.id }, - set: { roles }, - })); - await this.edgedb.run(query); - } - async delete(id: ID, _session: Session, _object: User): Promise { const query = e.delete(e.User, () => ({ filter_single: { id }, diff --git a/src/components/user/user.module.ts b/src/components/user/user.module.ts index eeee38c96a..f4cdfeab2e 100644 --- a/src/components/user/user.module.ts +++ b/src/components/user/user.module.ts @@ -9,6 +9,7 @@ import { PartnerModule } from '../partner/partner.module'; import { TimeZoneModule } from '../timezone'; import { AssignableRolesResolver } from './assignable-roles.resolver'; import { EducationModule } from './education/education.module'; +import { KnownLanguageRepository } from './known-language.repository'; import { KnownLanguageResolver } from './known-language.resolver'; import { UnavailabilityModule } from './unavailability/unavailability.module'; import { UserEdgedbRepository } from './user.edgedb.repository'; @@ -36,6 +37,7 @@ import { UserService } from './user.service'; UserLoader, UserService, splitDb(UserRepository, UserEdgedbRepository), + KnownLanguageRepository, ], exports: [UserService, UserRepository, EducationModule, UnavailabilityModule], }) diff --git a/src/components/user/user.repository.ts b/src/components/user/user.repository.ts index 7b704c16b6..2604a94970 100644 --- a/src/components/user/user.repository.ts +++ b/src/components/user/user.repository.ts @@ -29,8 +29,6 @@ import { Role } from '../authorization'; import { AssignOrganizationToUser, CreatePerson, - KnownLanguage, - LanguageProficiency, RemoveOrganizationFromUser, UpdateUser, User, @@ -123,7 +121,7 @@ export class UserRepository extends DtoRepository( ); } - async updateEmail( + private async updateEmail( user: User, email: string | null | undefined, ): Promise { @@ -153,7 +151,7 @@ export class UserRepository extends DtoRepository( } } - async updateRoles(user: User, roles: Role[]): Promise { + private async updateRoles(user: User, roles: Role[]): Promise { const removals = difference(user.roles.value, roles); const additions = difference(roles, user.roles.value); @@ -220,68 +218,6 @@ export class UserRepository extends DtoRepository( return result!; // result from paginate() will always have 1 row. } - async createKnownLanguage( - userId: ID, - languageId: ID, - languageProficiency: LanguageProficiency, - ): Promise { - await this.db - .query() - .matchNode('user', 'User', { id: userId }) - .matchNode('language', 'Language', { id: languageId }) - .create([ - node('user'), - relation('out', '', 'knownLanguage', { - active: true, - createdAt: DateTime.local(), - value: languageProficiency, - }), - node('language'), - ]) - .run(); - } - - async deleteKnownLanguage( - userId: ID, - languageId: ID, - languageProficiency: LanguageProficiency, - ): Promise { - await this.db - .query() - .matchNode('user', 'User', { id: userId }) - .matchNode('language', 'Language', { id: languageId }) - .match([ - [ - node('user'), - relation('out', 'rel', 'knownLanguage', { - active: true, - value: languageProficiency, - }), - node('language'), - ], - ]) - .setValues({ - 'rel.active': false, - }) - .run(); - } - - async listKnownLanguages(userId: ID, _session: Session) { - const results = await this.db - .query() - .match([ - node('node', 'Language'), - relation('in', 'knownLanguageRel', 'knownLanguage', ACTIVE), - node('user', 'User', { id: userId }), - ]) - .with('collect(distinct user) as users, node, knownLanguageRel') - .raw(`unwind users as user`) - .return(['knownLanguageRel.value as proficiency', 'node.id as language']) - .asResult() - .run(); - return results; - } - async doesEmailAddressExist(email: string) { const result = await this.db .query() diff --git a/src/components/user/user.resolver.ts b/src/components/user/user.resolver.ts index 3171858f49..d33a1b9ed4 100644 --- a/src/components/user/user.resolver.ts +++ b/src/components/user/user.resolver.ts @@ -295,16 +295,10 @@ export class UserResolver { }) async createKnownLanguage( @LoggedInSession() session: Session, - @Args() - { userId, languageId, languageProficiency }: ModifyKnownLanguageArgs, + @Args() args: ModifyKnownLanguageArgs, ): Promise { - await this.userService.createKnownLanguage( - userId, - languageId, - languageProficiency, - session, - ); - return await this.userService.readOne(userId, session); + await this.userService.createKnownLanguage(args); + return await this.userService.readOne(args.userId, session); } @Mutation(() => User, { @@ -312,15 +306,9 @@ export class UserResolver { }) async deleteKnownLanguage( @LoggedInSession() session: Session, - @Args() - { userId, languageId, languageProficiency }: ModifyKnownLanguageArgs, + @Args() args: ModifyKnownLanguageArgs, ): Promise { - await this.userService.deleteKnownLanguage( - userId, - languageId, - languageProficiency, - session, - ); - return await this.userService.readOne(userId, session); + await this.userService.deleteKnownLanguage(args); + return await this.userService.readOne(args.userId, session); } } diff --git a/src/components/user/user.service.ts b/src/components/user/user.service.ts index bd8985dd1c..971d12dfac 100644 --- a/src/components/user/user.service.ts +++ b/src/components/user/user.service.ts @@ -14,7 +14,6 @@ import { property } from '../../core/database/query'; import { mapListResults } from '../../core/database/results'; import { Privileges, Role } from '../authorization'; import { AssignableRoles } from '../authorization/dto/assignable-roles'; -import { LanguageService } from '../language'; import { LocationListInput, LocationService, @@ -33,19 +32,19 @@ import { import { AssignOrganizationToUser, CreatePerson, - KnownLanguage, + ModifyKnownLanguageArgs, RemoveOrganizationFromUser, UpdateUser, User, UserListInput, UserListOutput, } from './dto'; -import { LanguageProficiency } from './dto/language-proficiency.enum'; import { EducationListInput, EducationService, SecuredEducationList, } from './education'; +import { KnownLanguageRepository } from './known-language.repository'; import { SecuredUnavailabilityList, UnavailabilityListInput, @@ -63,7 +62,7 @@ export class UserService { private readonly unavailabilities: UnavailabilityService, private readonly privileges: Privileges, private readonly locationService: LocationService, - private readonly languageService: LanguageService, + private readonly knownLanguages: KnownLanguageRepository, private readonly userRepo: UserRepository, @Logger('user:service') private readonly logger: ILogger, ) {} @@ -303,56 +302,21 @@ export class UserService { ); } - async createKnownLanguage( - userId: ID, - languageId: ID, - languageProficiency: LanguageProficiency, - _session: Session, - ): Promise { - try { - await this.deleteKnownLanguage( - userId, - languageId, - languageProficiency, - _session, - ); - await this.userRepo.createKnownLanguage( - userId, - languageId, - languageProficiency, - ); - } catch (e) { - throw new ServerException('Could not create known language', e); - } + async createKnownLanguage(args: ModifyKnownLanguageArgs) { + await this.knownLanguages.create(args); } - async deleteKnownLanguage( - userId: ID, - languageId: ID, - languageProficiency: LanguageProficiency, - _session: Session, - ): Promise { - try { - await this.userRepo.deleteKnownLanguage( - userId, - languageId, - languageProficiency, - ); - } catch (e) { - throw new ServerException('Could not delete known language', e); - } + async deleteKnownLanguage(args: ModifyKnownLanguageArgs) { + await this.knownLanguages.delete(args); } - async listKnownLanguages( - userId: ID, - session: Session, - ): Promise { + async listKnownLanguages(userId: ID, session: Session) { const user = await this.userRepo.readOne(userId, session); const perms = this.privileges.for(session, User, user).all.knownLanguage; if (!perms.read) { return []; } - return await this.userRepo.listKnownLanguages(userId, session); + return await this.knownLanguages.list(userId); } async checkEmail(email: string): Promise {