chore: refacto NestJS in modules (#308)

* chore: wip refacto in modules

* fix: rollback port

* fix: jwt guard in wrong folder

* chore: rename folder exception-filter in filters

* fix: tests are running

* fix: excessive stack depth comparing types

* fix: auth issue

* chore: move createUser in UserService

* fix: test

* fix: guards

* fix: jwt guard don't handle falsy user
This commit is contained in:
Jérémy M
2023-06-16 10:38:11 +02:00
committed by GitHub
parent 5921c7f11d
commit 2cd081234f
1084 changed files with 2251 additions and 758 deletions

View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserRelationsResolver } from './user-relations.resolver';
import { UserService } from './user.service';
describe('UserRelationsResolver', () => {
let resolver: UserRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRelationsResolver,
{
provide: UserService,
useValue: {},
},
],
}).compile();
resolver = module.get<UserRelationsResolver>(UserRelationsResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,46 @@
import * as TypeGraphQL from '@nestjs/graphql';
import type { GraphQLResolveInfo } from 'graphql';
import { WorkspaceMember } from 'src/core/@generated/workspace-member/workspace-member.model';
import { User } from 'src/core/@generated/user/user.model';
import { Company } from 'src/core/@generated/company/company.model';
import { FindManyCompanyArgs } from 'src/core/@generated/company/find-many-company.args';
import { UserService } from './user.service';
@TypeGraphQL.Resolver(() => User)
export class UserRelationsResolver {
constructor(private readonly userService: UserService) {}
@TypeGraphQL.ResolveField(() => WorkspaceMember, {
nullable: true,
})
async workspaceMember(
@TypeGraphQL.Parent() user: User,
): Promise<WorkspaceMember | null> {
return await this.userService
.findFirst({
where: {
id: user.id,
},
})
.workspaceMember({});
}
@TypeGraphQL.ResolveField(() => [Company], {
nullable: false,
})
async companies(
@TypeGraphQL.Parent() user: User,
@TypeGraphQL.Info() info: GraphQLResolveInfo,
@TypeGraphQL.Args() args: FindManyCompanyArgs,
): Promise<Company[]> {
return this.userService
.findUniqueOrThrow({
where: {
id: user.id,
},
})
.companies({
...args,
});
}
}

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { UserRelationsResolver } from './user-relations.resolver';
import { WorkspaceModule } from '../workspace/workspace.module';
@Module({
imports: [WorkspaceModule],
providers: [UserService, UserResolver, UserRelationsResolver],
exports: [UserService],
})
export class UserModule {}

View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserResolver } from './user.resolver';
import { UserService } from './user.service';
describe('UserResolver', () => {
let resolver: UserResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserResolver,
{
provide: UserService,
useValue: {},
},
],
}).compile();
resolver = module.get<UserResolver>(UserResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,34 @@
import { Args, Resolver, Query } from '@nestjs/graphql';
import { UserService } from './user.service';
import { FindManyUserArgs } from 'src/core/@generated/user/find-many-user.args';
import { Workspace } from '@prisma/client';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { User } from 'src/core/@generated/user/user.model';
import { ExceptionFilter } from 'src/filters/exception.filter';
import { UseFilters, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
@UseGuards(JwtAuthGuard)
@Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
@UseFilters(ExceptionFilter)
@Query(() => [User], {
nullable: false,
})
async findManyUser(
@Args() args: FindManyUserArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<User[]> {
return await this.userService.findMany({
...args,
where: {
...args.where,
workspaceMember: {
is: { workspace: { is: { id: { equals: workspace.id } } } },
},
},
});
}
}

View File

@ -0,0 +1,36 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { PrismaService } from 'src/database/prisma.service';
import { prismaMock } from 'src/prisma-mock/jest-prisma-singleton';
import { WorkspaceService } from '../workspace/services/workspace.service';
import { WorkspaceMemberService } from '../workspace/services/workspace-member.service';
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: PrismaService,
useValue: prismaMock,
},
{
provide: WorkspaceService,
useValue: {},
},
{
provide: WorkspaceMemberService,
useValue: {},
},
],
}).compile();
service = module.get<UserService>(UserService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,109 @@
import {
BadRequestException,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { v4 } from 'uuid';
import { PrismaService } from 'src/database/prisma.service';
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
import { WorkspaceMemberService } from 'src/core/workspace/services/workspace-member.service';
export type UserPayload = {
firstName: string;
lastName: string;
email: string;
};
@Injectable()
export class UserService {
constructor(
private readonly prismaService: PrismaService,
private readonly workspaceService: WorkspaceService,
private readonly workspaceMemberService: WorkspaceMemberService,
) {}
// Find
findFirst = this.prismaService.user.findFirst;
findFirstOrThrow = this.prismaService.user.findFirstOrThrow;
findUnique = this.prismaService.user.findUnique;
findUniqueOrThrow = this.prismaService.user.findUniqueOrThrow;
findMany = this.prismaService.user.findMany;
// Create
create = this.prismaService.user.create;
createMany = this.prismaService.user.createMany;
// Update
update = this.prismaService.user.update;
upsert = this.prismaService.user.upsert;
updateMany = this.prismaService.user.updateMany;
// Delete
delete = this.prismaService.user.delete;
deleteMany = this.prismaService.user.deleteMany;
// Aggregate
aggregate = this.prismaService.user.aggregate;
// Count
count = this.prismaService.user.count;
// GroupBy
groupBy = this.prismaService.user.groupBy;
// Customs
async createUser(rawUser: UserPayload) {
if (!rawUser.email) {
throw new BadRequestException('Email is missing');
}
if (!rawUser.firstName || !rawUser.lastName) {
throw new BadRequestException('Firstname or lastname is missing');
}
const emailDomain = rawUser.email.split('@')[1];
if (!emailDomain) {
throw new BadRequestException('Email is malformed');
}
const workspace = await this.workspaceService.findUnique({
where: { domainName: emailDomain },
});
if (!workspace) {
throw new ForbiddenException(
'User email domain does not match an existing workspace',
);
}
const user = await this.prismaService.user.upsert({
where: {
email: rawUser.email,
},
create: {
id: v4(),
displayName: rawUser.firstName + ' ' + rawUser.lastName,
email: rawUser.email,
locale: 'en',
},
update: {},
});
await this.workspaceMemberService.upsert({
where: {
userId: user.id,
},
create: {
id: v4(),
userId: user.id,
workspaceId: workspace.id,
},
update: {},
});
return user;
}
}