Refactor backend and add exception handlers (#189)

This commit is contained in:
Charles Bochet
2023-06-04 00:21:36 +02:00
committed by GitHub
parent a2fe159c2c
commit bbc80cd543
35 changed files with 459 additions and 899 deletions

View File

@ -1,18 +1,17 @@
import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { CompanyCreateWithoutPeopleInput } from './company-create-without-people.input';
import { Type } from 'class-transformer';
import { HideField } from '@nestjs/graphql';
import { CompanyCreateOrConnectWithoutPeopleInput } from './company-create-or-connect-without-people.input';
import { CompanyWhereUniqueInput } from './company-where-unique.input';
import { Type } from 'class-transformer';
@InputType()
export class CompanyCreateNestedOneWithoutPeopleInput {
@Field(() => CompanyCreateWithoutPeopleInput, { nullable: true })
@Type(() => CompanyCreateWithoutPeopleInput)
@HideField()
create?: CompanyCreateWithoutPeopleInput;
@Field(() => CompanyCreateOrConnectWithoutPeopleInput, { nullable: true })
@Type(() => CompanyCreateOrConnectWithoutPeopleInput)
@HideField()
connectOrCreate?: CompanyCreateOrConnectWithoutPeopleInput;
@Field(() => CompanyWhereUniqueInput, { nullable: true })

View File

@ -1,37 +1,34 @@
import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { CompanyCreateWithoutPeopleInput } from './company-create-without-people.input';
import { Type } from 'class-transformer';
import { HideField } from '@nestjs/graphql';
import { CompanyCreateOrConnectWithoutPeopleInput } from './company-create-or-connect-without-people.input';
import { CompanyUpsertWithoutPeopleInput } from './company-upsert-without-people.input';
import { CompanyWhereUniqueInput } from './company-where-unique.input';
import { Type } from 'class-transformer';
import { CompanyUpdateWithoutPeopleInput } from './company-update-without-people.input';
@InputType()
export class CompanyUpdateOneWithoutPeopleNestedInput {
@Field(() => CompanyCreateWithoutPeopleInput, { nullable: true })
@Type(() => CompanyCreateWithoutPeopleInput)
@HideField()
create?: CompanyCreateWithoutPeopleInput;
@Field(() => CompanyCreateOrConnectWithoutPeopleInput, { nullable: true })
@Type(() => CompanyCreateOrConnectWithoutPeopleInput)
@HideField()
connectOrCreate?: CompanyCreateOrConnectWithoutPeopleInput;
@Field(() => CompanyUpsertWithoutPeopleInput, { nullable: true })
@Type(() => CompanyUpsertWithoutPeopleInput)
@HideField()
upsert?: CompanyUpsertWithoutPeopleInput;
@Field(() => Boolean, { nullable: true })
@HideField()
disconnect?: boolean;
@Field(() => Boolean, { nullable: true })
@HideField()
delete?: boolean;
@Field(() => CompanyWhereUniqueInput, { nullable: true })
@Type(() => CompanyWhereUniqueInput)
connect?: CompanyWhereUniqueInput;
@Field(() => CompanyUpdateWithoutPeopleInput, { nullable: true })
@Type(() => CompanyUpdateWithoutPeopleInput)
@HideField()
update?: CompanyUpdateWithoutPeopleInput;
}

View File

@ -1,23 +1,21 @@
import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { PersonCreateWithoutCompanyInput } from './person-create-without-company.input';
import { Type } from 'class-transformer';
import { HideField } from '@nestjs/graphql';
import { PersonCreateOrConnectWithoutCompanyInput } from './person-create-or-connect-without-company.input';
import { PersonCreateManyCompanyInputEnvelope } from './person-create-many-company-input-envelope.input';
import { PersonWhereUniqueInput } from './person-where-unique.input';
import { Type } from 'class-transformer';
@InputType()
export class PersonCreateNestedManyWithoutCompanyInput {
@Field(() => [PersonCreateWithoutCompanyInput], { nullable: true })
@Type(() => PersonCreateWithoutCompanyInput)
@HideField()
create?: Array<PersonCreateWithoutCompanyInput>;
@Field(() => [PersonCreateOrConnectWithoutCompanyInput], { nullable: true })
@Type(() => PersonCreateOrConnectWithoutCompanyInput)
@HideField()
connectOrCreate?: Array<PersonCreateOrConnectWithoutCompanyInput>;
@Field(() => PersonCreateManyCompanyInputEnvelope, { nullable: true })
@Type(() => PersonCreateManyCompanyInputEnvelope)
@HideField()
createMany?: PersonCreateManyCompanyInputEnvelope;
@Field(() => [PersonWhereUniqueInput], { nullable: true })

View File

@ -1,64 +1,49 @@
import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { PersonCreateWithoutCompanyInput } from './person-create-without-company.input';
import { Type } from 'class-transformer';
import { HideField } from '@nestjs/graphql';
import { PersonCreateOrConnectWithoutCompanyInput } from './person-create-or-connect-without-company.input';
import { PersonUpsertWithWhereUniqueWithoutCompanyInput } from './person-upsert-with-where-unique-without-company.input';
import { PersonCreateManyCompanyInputEnvelope } from './person-create-many-company-input-envelope.input';
import { PersonWhereUniqueInput } from './person-where-unique.input';
import { Type } from 'class-transformer';
import { PersonUpdateWithWhereUniqueWithoutCompanyInput } from './person-update-with-where-unique-without-company.input';
import { PersonUpdateManyWithWhereWithoutCompanyInput } from './person-update-many-with-where-without-company.input';
import { PersonScalarWhereInput } from './person-scalar-where.input';
@InputType()
export class PersonUpdateManyWithoutCompanyNestedInput {
@Field(() => [PersonCreateWithoutCompanyInput], { nullable: true })
@Type(() => PersonCreateWithoutCompanyInput)
@HideField()
create?: Array<PersonCreateWithoutCompanyInput>;
@Field(() => [PersonCreateOrConnectWithoutCompanyInput], { nullable: true })
@Type(() => PersonCreateOrConnectWithoutCompanyInput)
@HideField()
connectOrCreate?: Array<PersonCreateOrConnectWithoutCompanyInput>;
@Field(() => [PersonUpsertWithWhereUniqueWithoutCompanyInput], {
nullable: true,
})
@Type(() => PersonUpsertWithWhereUniqueWithoutCompanyInput)
@HideField()
upsert?: Array<PersonUpsertWithWhereUniqueWithoutCompanyInput>;
@Field(() => PersonCreateManyCompanyInputEnvelope, { nullable: true })
@Type(() => PersonCreateManyCompanyInputEnvelope)
@HideField()
createMany?: PersonCreateManyCompanyInputEnvelope;
@Field(() => [PersonWhereUniqueInput], { nullable: true })
@Type(() => PersonWhereUniqueInput)
@HideField()
set?: Array<PersonWhereUniqueInput>;
@Field(() => [PersonWhereUniqueInput], { nullable: true })
@Type(() => PersonWhereUniqueInput)
@HideField()
disconnect?: Array<PersonWhereUniqueInput>;
@Field(() => [PersonWhereUniqueInput], { nullable: true })
@Type(() => PersonWhereUniqueInput)
@HideField()
delete?: Array<PersonWhereUniqueInput>;
@Field(() => [PersonWhereUniqueInput], { nullable: true })
@Type(() => PersonWhereUniqueInput)
connect?: Array<PersonWhereUniqueInput>;
@Field(() => [PersonUpdateWithWhereUniqueWithoutCompanyInput], {
nullable: true,
})
@Type(() => PersonUpdateWithWhereUniqueWithoutCompanyInput)
@HideField()
update?: Array<PersonUpdateWithWhereUniqueWithoutCompanyInput>;
@Field(() => [PersonUpdateManyWithWhereWithoutCompanyInput], {
nullable: true,
})
@Type(() => PersonUpdateManyWithWhereWithoutCompanyInput)
@HideField()
updateMany?: Array<PersonUpdateManyWithWhereWithoutCompanyInput>;
@Field(() => [PersonScalarWhereInput], { nullable: true })
@Type(() => PersonScalarWhereInput)
@HideField()
deleteMany?: Array<PersonScalarWhereInput>;
}

View File

@ -1,18 +1,17 @@
import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { UserCreateWithoutCompaniesInput } from './user-create-without-companies.input';
import { Type } from 'class-transformer';
import { HideField } from '@nestjs/graphql';
import { UserCreateOrConnectWithoutCompaniesInput } from './user-create-or-connect-without-companies.input';
import { UserWhereUniqueInput } from './user-where-unique.input';
import { Type } from 'class-transformer';
@InputType()
export class UserCreateNestedOneWithoutCompaniesInput {
@Field(() => UserCreateWithoutCompaniesInput, { nullable: true })
@Type(() => UserCreateWithoutCompaniesInput)
@HideField()
create?: UserCreateWithoutCompaniesInput;
@Field(() => UserCreateOrConnectWithoutCompaniesInput, { nullable: true })
@Type(() => UserCreateOrConnectWithoutCompaniesInput)
@HideField()
connectOrCreate?: UserCreateOrConnectWithoutCompaniesInput;
@Field(() => UserWhereUniqueInput, { nullable: true })

View File

@ -1,37 +1,34 @@
import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { UserCreateWithoutCompaniesInput } from './user-create-without-companies.input';
import { Type } from 'class-transformer';
import { HideField } from '@nestjs/graphql';
import { UserCreateOrConnectWithoutCompaniesInput } from './user-create-or-connect-without-companies.input';
import { UserUpsertWithoutCompaniesInput } from './user-upsert-without-companies.input';
import { UserWhereUniqueInput } from './user-where-unique.input';
import { Type } from 'class-transformer';
import { UserUpdateWithoutCompaniesInput } from './user-update-without-companies.input';
@InputType()
export class UserUpdateOneWithoutCompaniesNestedInput {
@Field(() => UserCreateWithoutCompaniesInput, { nullable: true })
@Type(() => UserCreateWithoutCompaniesInput)
@HideField()
create?: UserCreateWithoutCompaniesInput;
@Field(() => UserCreateOrConnectWithoutCompaniesInput, { nullable: true })
@Type(() => UserCreateOrConnectWithoutCompaniesInput)
@HideField()
connectOrCreate?: UserCreateOrConnectWithoutCompaniesInput;
@Field(() => UserUpsertWithoutCompaniesInput, { nullable: true })
@Type(() => UserUpsertWithoutCompaniesInput)
@HideField()
upsert?: UserUpsertWithoutCompaniesInput;
@Field(() => Boolean, { nullable: true })
@HideField()
disconnect?: boolean;
@Field(() => Boolean, { nullable: true })
@HideField()
delete?: boolean;
@Field(() => UserWhereUniqueInput, { nullable: true })
@Type(() => UserWhereUniqueInput)
connect?: UserWhereUniqueInput;
@Field(() => UserUpdateWithoutCompaniesInput, { nullable: true })
@Type(() => UserUpdateWithoutCompaniesInput)
@HideField()
update?: UserUpdateWithoutCompaniesInput;
}

View File

@ -22,6 +22,7 @@ import { CompanyRelationsResolver } from './resolvers/relations/company-relation
import { CommentThreadRelationsResolver } from './resolvers/relations/comment-thread-relations.resolver';
import { PipelineRelationsResolver } from './resolvers/relations/pipeline-relations.resolver';
import { PipelineStageRelationsResolver } from './resolvers/relations/pipeline-stage-relations.resolver';
import { GraphQLError } from 'graphql';
@Module({
imports: [
@ -29,6 +30,10 @@ import { PipelineStageRelationsResolver } from './resolvers/relations/pipeline-s
context: ({ req }) => ({ req }),
driver: ApolloDriver,
autoSchemaFile: true,
formatError: (error: GraphQLError) => {
error.extensions.stacktrace = undefined;
return error;
},
}),
AuthModule,
PrismaModule,

View File

@ -11,10 +11,12 @@ import { AffectedRows } from '../@generated/prisma/affected-rows.output';
import { DeleteManyCompanyArgs } from '../@generated/company/delete-many-company.args';
import { Workspace } from '@prisma/client';
import { ArgsService } from './services/args.service';
import { CheckWorkspaceOwnership } from 'src/auth/guards/check-workspace-ownership.guard';
import { Prisma } from '@prisma/client';
import { UpdateOneGuard } from './guards/update-one.guard';
import { DeleteManyGuard } from './guards/delete-many.guard';
import { CreateOneGuard } from './guards/create-one.guard';
@UseGuards(JwtAuthGuard, CheckWorkspaceOwnership)
@UseGuards(JwtAuthGuard)
@Resolver(() => Company)
export class CompanyResolver {
constructor(
@ -35,6 +37,7 @@ export class CompanyResolver {
return this.prismaService.company.findMany(preparedArgs);
}
@UseGuards(UpdateOneGuard)
@Mutation(() => Company, {
nullable: true,
})
@ -50,6 +53,7 @@ export class CompanyResolver {
} satisfies UpdateOneCompanyArgs as Prisma.CompanyUpdateArgs);
}
@UseGuards(DeleteManyGuard)
@Mutation(() => AffectedRows, {
nullable: false,
})
@ -61,6 +65,7 @@ export class CompanyResolver {
});
}
@UseGuards(CreateOneGuard)
@Mutation(() => Company, {
nullable: false,
})

View File

@ -0,0 +1,18 @@
import { Catch, HttpException } from '@nestjs/common';
import { GqlExceptionFilter } from '@nestjs/graphql';
import { Prisma } from '@prisma/client';
import { GraphQLError } from 'graphql';
@Catch()
export class ExceptionFilter implements GqlExceptionFilter {
catch(exception: HttpException) {
if (exception instanceof Prisma.PrismaClientValidationError) {
throw new GraphQLError('Invalid request', {
extensions: {
code: 'INVALID_REQUEST',
},
});
}
return exception;
}
}

View File

@ -0,0 +1,12 @@
import { CanActivate, Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CreateOneGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(): Promise<boolean> {
// TODO
return true;
}
}

View File

@ -0,0 +1,12 @@
import { CanActivate, Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class DeleteManyGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(): Promise<boolean> {
// TODO
return true;
}
}

View File

@ -0,0 +1,50 @@
import {
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class UpdateOneGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
const entity = gqlContext.getArgByIndex(3).returnType?.name;
const args = gqlContext.getArgs();
console.log(args.data);
if (!entity || !args.where?.id) {
throw new HttpException(
{ reason: 'Invalid Request' },
HttpStatus.BAD_REQUEST,
);
}
const object = await this.prismaService[entity].findUniqueOrThrow({
where: { id: args.where.id },
});
if (!object) {
throw new HttpException(
{ reason: 'Record not found' },
HttpStatus.NOT_FOUND,
);
}
const workspace = await request.workspace;
if (object.workspaceId !== workspace.id) {
throw new HttpException(
{ reason: 'Record not found' },
HttpStatus.NOT_FOUND,
);
}
return true;
}
}

View File

@ -11,10 +11,12 @@ import { DeleteManyPersonArgs } from '../@generated/person/delete-many-person.ar
import { Workspace } from '../@generated/workspace/workspace.model';
import { AuthWorkspace } from './decorators/auth-workspace.decorator';
import { ArgsService } from './services/args.service';
import { CheckWorkspaceOwnership } from 'src/auth/guards/check-workspace-ownership.guard';
import { Prisma } from '@prisma/client';
import { UpdateOneGuard } from './guards/update-one.guard';
import { DeleteManyGuard } from './guards/delete-many.guard';
import { CreateOneGuard } from './guards/create-one.guard';
@UseGuards(JwtAuthGuard, CheckWorkspaceOwnership)
@UseGuards(JwtAuthGuard)
@Resolver(() => Person)
export class PersonResolver {
constructor(
@ -39,6 +41,7 @@ export class PersonResolver {
});
}
@UseGuards(UpdateOneGuard)
@Mutation(() => Person, {
nullable: true,
})
@ -54,6 +57,7 @@ export class PersonResolver {
} satisfies UpdateOnePersonArgs as Prisma.PersonUpdateArgs);
}
@UseGuards(DeleteManyGuard)
@Mutation(() => AffectedRows, {
nullable: false,
})
@ -65,6 +69,7 @@ export class PersonResolver {
});
}
@UseGuards(CreateOneGuard)
@Mutation(() => Person, {
nullable: false,
})

View File

@ -1,23 +1,20 @@
import { Resolver, Query, Args } from '@nestjs/graphql';
import { PrismaService } from 'src/database/prisma.service';
import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/auth/guards/jwt.auth.guard';
import { UseFilters, UseGuards } from '@nestjs/common';
import { User } from '../@generated/user/user.model';
import { FindManyUserArgs } from '../@generated/user/find-many-user.args';
import { Workspace } from '@prisma/client';
import { AuthWorkspace } from './decorators/auth-workspace.decorator';
import { ArgsService } from './services/args.service';
import { CheckWorkspaceOwnership } from 'src/auth/guards/check-workspace-ownership.guard';
import { ExceptionFilter } from './exception-filters/exception.filter';
import { JwtAuthGuard } from 'src/auth/guards/jwt.auth.guard';
@UseGuards(JwtAuthGuard, CheckWorkspaceOwnership)
@UseGuards(JwtAuthGuard)
@Resolver(() => User)
export class UserResolver {
constructor(
private readonly prismaService: PrismaService,
private readonly argsService: ArgsService,
) {}
constructor(private readonly prismaService: PrismaService) {}
@UseFilters(ExceptionFilter)
@Query(() => [User], {
nullable: false,
})

View File

@ -1,20 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
it('should be defined', () => {
expect(appController).toBeDefined();
});
});

View File

@ -1,7 +0,0 @@
import { Controller } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
}

View File

@ -1,5 +1,4 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HealthController } from './health.controller';
import { TerminusModule } from '@nestjs/terminus';
@ -9,7 +8,7 @@ import { ConfigModule } from '@nestjs/config';
import { ApiModule } from './api/api.module';
@Module({
imports: [ConfigModule.forRoot({}), TerminusModule, AuthModule, ApiModule],
controllers: [AppController, HealthController],
controllers: [HealthController],
providers: [AppService],
})
export class AppModule {}

View File

@ -5,10 +5,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
import { AuthService } from './services/auth.service';
import { GoogleAuthController } from './google.auth.controller';
import { GoogleStrategy } from './strategies/google.auth.strategy';
import { AuthController } from './auth.controller';
import { UserRepository } from 'src/entities/user/user.repository';
import { WorkspaceRepository } from 'src/entities/workspace/workspace.repository';
import { RefreshTokenRepository } from 'src/entities/refresh-token/refresh-token.repository';
import { TokenController } from './token.controller';
import { PrismaService } from 'src/database/prisma.service';
const jwtModule = JwtModule.registerAsync({
@ -26,16 +23,8 @@ const jwtModule = JwtModule.registerAsync({
@Module({
imports: [jwtModule, ConfigModule.forRoot({})],
controllers: [GoogleAuthController, AuthController],
providers: [
AuthService,
JwtAuthStrategy,
GoogleStrategy,
UserRepository,
WorkspaceRepository,
RefreshTokenRepository,
PrismaService,
],
controllers: [GoogleAuthController, TokenController],
providers: [AuthService, JwtAuthStrategy, GoogleStrategy, PrismaService],
exports: [jwtModule],
})
export class AuthModule {}

View File

@ -26,7 +26,7 @@ export class GoogleAuthController {
@Get('redirect')
@UseGuards(AuthGuard('google'))
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
const user = await this.authService.upsertUser(req.user);
const user = await this.authService.createUser(req.user);
if (!user) {
throw new HttpException(

View File

@ -1,85 +0,0 @@
import {
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Request } from 'express';
import { PrismaService } from 'src/database/prisma.service';
type OperationEntity = {
operation?: string;
entity?: string;
};
@Injectable()
export class CheckWorkspaceOwnership implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
const { operation, entity } = this.fetchOperationAndEntity(request);
const variables = request.body.variables;
const workspace = await request.workspace;
if (!entity || !operation) {
return false;
}
if (operation === 'updateOne') {
const object = await this.prismaService[entity].findUniqueOrThrow({
where: { id: variables.id },
});
if (!object) {
throw new HttpException(
{ reason: 'Record not found' },
HttpStatus.NOT_FOUND,
);
}
if (object.workspaceId !== workspace.id) {
throw new HttpException(
{ reason: 'Record not found' },
HttpStatus.NOT_FOUND,
);
}
return true;
}
if (operation === 'deleteMany') {
// TODO: write this logic
return true;
}
if (operation === 'findMany') {
return true;
}
if (operation === 'createOne') {
return true;
}
return false;
}
private fetchOperationAndEntity(request: Request): OperationEntity {
if (!request.body.operationName) {
return { operation: undefined, entity: undefined };
}
const regex =
/(updateOne|deleteMany|createOne|findMany)(Person|Company|User)/i;
const match = request.body.query.match(regex);
if (match) {
return {
operation: match[1],
entity: match[2].toLowerCase(),
};
}
return { operation: undefined, entity: undefined };
}
}

View File

@ -2,11 +2,9 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { JwtPayload } from '../strategies/jwt.auth.strategy';
import { ConfigService } from '@nestjs/config';
import { UserRepository } from 'src/entities/user/user.repository';
import { WorkspaceRepository } from 'src/entities/workspace/workspace.repository';
import { RefreshTokenRepository } from 'src/entities/refresh-token/refresh-token.repository';
import { v4 } from 'uuid';
import { RefreshToken, User } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
export type UserPayload = {
firstName: string;
@ -19,12 +17,10 @@ export class AuthService {
constructor(
private jwtService: JwtService,
private configService: ConfigService,
private userRepository: UserRepository,
private workspaceRepository: WorkspaceRepository,
private refreshTokenRepository: RefreshTokenRepository,
private prismaService: PrismaService,
) {}
async upsertUser(rawUser: UserPayload) {
async createUser(rawUser: UserPayload) {
if (!rawUser.email) {
throw new HttpException(
{ reason: 'Email is missing' },
@ -48,7 +44,7 @@ export class AuthService {
);
}
const workspace = await this.workspaceRepository.findUnique({
const workspace = await this.prismaService.workspace.findUnique({
where: { domainName: emailDomain },
});
@ -59,50 +55,66 @@ export class AuthService {
);
}
const user = await this.userRepository.upsertUser({
data: {
id: v4(),
const user = await this.prismaService.user.upsert({
where: {
email: rawUser.email,
},
create: {
id: v4(),
displayName: rawUser.firstName + ' ' + rawUser.lastName,
email: rawUser.email,
locale: 'en',
},
workspaceId: workspace.id,
update: {},
});
await this.userRepository.upsertWorkspaceMember({
data: {
await this.prismaService.workspaceMember.upsert({
where: {
userId: user.id,
},
create: {
id: v4(),
userId: user.id,
workspaceId: workspace.id,
},
update: {},
});
return user;
}
async generateAccessToken(refreshToken: string): Promise<string | undefined> {
const refreshTokenObject = await this.refreshTokenRepository.findFirst({
const refreshTokenObject = await this.prismaService.refreshToken.findFirst({
where: { refreshToken: refreshToken },
});
if (!refreshTokenObject) {
return;
throw new HttpException(
{ reason: 'Invalid Refresh token' },
HttpStatus.FORBIDDEN,
);
}
const user = await this.userRepository.findUnique({
const user = await this.prismaService.user.findUnique({
where: { id: refreshTokenObject.userId },
});
if (!user) {
return;
throw new HttpException(
{ reason: 'Refresh token is not associated to a valid user' },
HttpStatus.FORBIDDEN,
);
}
const workspace = await this.workspaceRepository.findFirst({
const workspace = await this.prismaService.workspace.findFirst({
where: { workspaceMember: { some: { userId: user.id } } },
});
if (!workspace) {
return;
throw new HttpException(
{ reason: 'Refresh token is not associated to a valid workspace' },
HttpStatus.FORBIDDEN,
);
}
const payload: JwtPayload = {
@ -113,12 +125,16 @@ export class AuthService {
}
async registerRefreshToken(user: User): Promise<RefreshToken> {
const refreshToken = await this.refreshTokenRepository.upsertRefreshToken({
data: {
const refreshToken = await this.prismaService.refreshToken.upsert({
where: {
id: user.id,
},
create: {
id: v4(),
userId: user.id,
refreshToken: v4(),
},
update: {},
});
return refreshToken;

View File

@ -3,7 +3,7 @@ import { Request, Response } from 'express';
import { AuthService } from './services/auth.service';
@Controller('auth/token')
export class AuthController {
export class TokenController {
constructor(private authService: AuthService) {}
@Post()

View File

@ -12,35 +12,78 @@ generator nestgraphql {
provider = "node node_modules/prisma-nestjs-graphql"
output = "../../src/api/@generated"
decorate_1_type = "*CommentThreadTargetCreateNestedManyWithoutCommentThreadInput"
decorate_1_field = "!(createMany)"
decorate_1_name = "HideField"
decorate_1_from = "@nestjs/graphql"
decorate_1_arguments = "[]"
// CommentThread create: Only Allow targets createMany and comments createMany
decorate_createCommentThreadTargets_type = "*CommentThreadTargetCreateNestedManyWithoutCommentThreadInput"
decorate_createCommentThreadTargets_field = "!(createMany)"
decorate_createCommentThreadTargets_name = "HideField"
decorate_createCommentThreadTargets_from = "@nestjs/graphql"
decorate_createCommentThreadTargets_arguments = "[]"
decorate_2_type = "*CommentCreateNestedManyWithoutCommentThreadInput"
decorate_2_field = "!(createMany)"
decorate_2_name = "HideField"
decorate_2_from = "@nestjs/graphql"
decorate_2_arguments = "[]"
decorate_createCommentThreadComments_type = "*CommentCreateNestedManyWithoutCommentThreadInput"
decorate_createCommentThreadComments_field = "!(createMany)"
decorate_createCommentThreadComments_name = "HideField"
decorate_createCommentThreadComments_from = "@nestjs/graphql"
decorate_createCommentThreadComments_arguments = "[]"
decorate_3_type = "*UserCreateNestedOneWithoutCommentsInput"
decorate_3_field = "!(connect)"
decorate_3_name = "HideField"
decorate_3_from = "@nestjs/graphql"
decorate_3_arguments = "[]"
// Comment create: Only Allow author connect and commentThread connect
decorate_createCommentUser_type = "*UserCreateNestedOneWithoutCommentsInput"
decorate_createCommentUser_field = "!(connect)"
decorate_createCommentUser_name = "HideField"
decorate_createCommentUser_from = "@nestjs/graphql"
decorate_createCommentUser_arguments = "[]"
decorate_4_type = "*CommentThreadCreateNestedOneWithoutCommentsInput"
decorate_4_field = "!(connect)"
decorate_4_name = "HideField"
decorate_4_from = "@nestjs/graphql"
decorate_4_arguments = "[]"
decorate_createCommentCommentThread_type = "*CommentThreadCreateNestedOneWithoutCommentsInput"
decorate_createCommentCommentThread_field = "!(connect)"
decorate_createCommentCommentThread_name = "HideField"
decorate_createCommentCommentThread_from = "@nestjs/graphql"
decorate_createCommentCommentThread_arguments = "[]"
decorate_5_type = "!(*Aggregate*|*GroupBy*|*OrderBy*)"
decorate_5_field = "_count"
decorate_5_name = "HideField"
decorate_5_from = "@nestjs/graphql"
decorate_5_arguments = "[]"
// Person create: Only Allow company connect
decorate_createPersonCompany_type = "*CompanyCreateNestedOneWithoutPeopleInput"
decorate_createPersonCompany_field = "!(connect)"
decorate_createPersonCompany_name = "HideField"
decorate_createPersonCompany_from = "@nestjs/graphql"
decorate_createPersonCompany_arguments = "[]"
// Person update: Only Allow company connect
decorate_updatePersonCompany_type = "*CompanyUpdateOneWithoutPeopleNestedInput"
decorate_updatePersonCompany_field = "!(connect)"
decorate_updatePersonCompany_name = "HideField"
decorate_updatePersonCompany_from = "@nestjs/graphql"
decorate_updatePersonCompany_arguments = "[]"
// Company create: Only Allow people and accountOwner connect
decorate_createCompanyUser_type = "*UserCreateNestedOneWithoutCompaniesInput"
decorate_createCompanyUser_field = "!(connect)"
decorate_createCompanyUser_name = "HideField"
decorate_createCompanyUser_from = "@nestjs/graphql"
decorate_createCompanyUser_arguments = "[]"
decorate_createCompanyPerson_type = "*PersonCreateNestedManyWithoutCompanyInput"
decorate_createCompanyPerson_field = "!(connect)"
decorate_createCompanyPerson_name = "HideField"
decorate_createCompanyPerson_from = "@nestjs/graphql"
decorate_createCompanyPerson_arguments = "[]"
// Company update: Only Allow action on people and accountOwner
decorate_updateCompanyUser_type = "*UserUpdateOneWithoutCompaniesNestedInput"
decorate_updateCompanyUser_field = "!(connect)"
decorate_updateCompanyUser_name = "HideField"
decorate_updateCompanyUser_from = "@nestjs/graphql"
decorate_updateCompanyUser_arguments = "[]"
decorate_updateCompanyPerson_type = "*PersonUpdateManyWithoutCompanyNestedInput"
decorate_updateCompanyPerson_field = "!(connect)"
decorate_updateCompanyPerson_name = "HideField"
decorate_updateCompanyPerson_from = "@nestjs/graphql"
decorate_updateCompanyPerson_arguments = "[]"
// Disable _count on all models except Aggregation use case
decorate_count_type = "!(*Aggregate*|*GroupBy*|*OrderBy*)"
decorate_count_field = "_count"
decorate_count_name = "HideField"
decorate_count_from = "@nestjs/graphql"
decorate_count_arguments = "[]"
}
model User {

View File

@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { CompanyRepository } from './company.repository';
import { PrismaModule } from 'src/database/prisma.module';
@Module({
imports: [PrismaModule],
providers: [CompanyRepository],
exports: [CompanyRepository],
})
export class CompanyModule {}

View File

@ -1,25 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Company, Prisma } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CompanyRepository {
constructor(private prisma: PrismaService) {}
async findMany(params: {
skip?: number;
take?: number;
cursor?: Prisma.CompanyWhereUniqueInput;
where?: Prisma.CompanyWhereInput;
orderBy?: Prisma.CompanyOrderByWithRelationInput;
}): Promise<Company[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.company.findMany({ skip, take, cursor, where, orderBy });
}
async findOne(id: string | null) {
if (id === null) return null;
const company = await this.prisma.company.findUnique({ where: { id } });
return company;
}
}

View File

@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { PersonRepository } from './person.repository';
import { PrismaModule } from 'src/database/prisma.module';
@Module({
imports: [PrismaModule],
providers: [PersonRepository],
exports: [PersonRepository],
})
export class PersonModule {}

View File

@ -1,19 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Person, Prisma } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class PersonRepository {
constructor(private prisma: PrismaService) {}
async findMany(params: {
skip?: number;
take?: number;
cursor?: Prisma.PersonWhereUniqueInput;
where?: Prisma.PersonWhereInput;
orderBy?: Prisma.PersonOrderByWithRelationInput;
}): Promise<Person[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.person.findMany({ skip, take, cursor, where, orderBy });
}
}

View File

@ -1,32 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Prisma, RefreshToken } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class RefreshTokenRepository {
constructor(private prisma: PrismaService) {}
async upsertRefreshToken(params: {
data: Prisma.RefreshTokenUncheckedCreateInput;
}): Promise<RefreshToken> {
const { data } = params;
return await this.prisma.refreshToken.upsert({
where: {
id: data.id,
},
create: {
id: data.id,
userId: data.userId,
refreshToken: data.refreshToken,
},
update: {},
});
}
async findFirst(
data: Prisma.RefreshTokenFindFirstArgs,
): Promise<RefreshToken | null> {
return await this.prisma.refreshToken.findFirst(data);
}
}

View File

@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { PrismaModule } from 'src/database/prisma.module';
@Module({
imports: [PrismaModule],
providers: [UserRepository],
exports: [UserRepository],
})
export class UserModule {}

View File

@ -1,67 +0,0 @@
import { Injectable } from '@nestjs/common';
import { User, Prisma, WorkspaceMember } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class UserRepository {
constructor(private prisma: PrismaService) {}
async findMany(params: {
skip?: number;
take?: number;
cursor?: Prisma.UserWhereUniqueInput;
where?: Prisma.UserWhereInput;
orderBy?: Prisma.UserOrderByWithRelationInput;
}): Promise<User[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.user.findMany({ skip, take, cursor, where, orderBy });
}
async findUnique(params: {
where?: Prisma.UserWhereInput;
}): Promise<User | null> {
const { where } = params;
return this.prisma.user.findFirst({
where,
});
}
async upsertUser(params: {
data: Prisma.UserCreateInput;
workspaceId: string;
}): Promise<User> {
const { data } = params;
return await this.prisma.user.upsert({
where: {
email: data.email,
},
create: {
id: data.id,
displayName: data.displayName,
email: data.email,
locale: data.locale,
},
update: {},
});
}
async upsertWorkspaceMember(params: {
data: Prisma.WorkspaceMemberUncheckedCreateInput;
}): Promise<WorkspaceMember> {
const { data } = params;
return await this.prisma.workspaceMember.upsert({
where: {
userId: data.userId,
},
create: {
id: data.id,
userId: data.userId,
workspaceId: data.workspaceId,
},
update: {},
});
}
}

View File

@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { WorkspaceRepository } from './workspace.repository';
import { PrismaModule } from 'src/database/prisma.module';
@Module({
imports: [PrismaModule],
providers: [WorkspaceRepository],
exports: [WorkspaceRepository],
})
export class WorkspaceModule {}

View File

@ -1,37 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Workspace, Prisma } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class WorkspaceRepository {
constructor(private prisma: PrismaService) {}
async findMany(params: {
skip?: number;
take?: number;
cursor?: Prisma.WorkspaceWhereUniqueInput;
where?: Prisma.WorkspaceWhereInput;
orderBy?: Prisma.WorkspaceOrderByWithRelationInput;
}): Promise<Workspace[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.workspace.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async findUnique(
params: Prisma.WorkspaceFindUniqueArgs,
): Promise<Workspace | null> {
return await this.prisma.workspace.findUnique(params);
}
async findFirst(
params: Prisma.WorkspaceFindFirstArgs,
): Promise<Workspace | null> {
return await this.prisma.workspace.findFirst(params);
}
}

View File

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HealthController } from './health.controller';
import { AppService } from './app.service';
import { TerminusModule } from '@nestjs/terminus';
describe('HealthController', () => {
let healthController: HealthController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [HealthController],
imports: [TerminusModule],
providers: [AppService],
}).compile();
healthController = app.get<HealthController>(HealthController);
});
it('should be defined', () => {
expect(healthController).toBeDefined();
});
});

View File

@ -5,4 +5,5 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
await app.listen(3000);
}
bootstrap();