Removing Prisma and Grapql-nestjs-prisma resolvers (#2574)
* Some cleaning * Fix seeds * Fix all sign in, sign up flow and apiKey optimistic rendering * Fix
This commit is contained in:
@ -1,15 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { ActivityResolver } from './resolvers/activity.resolver';
|
||||
import { ActivityService } from './services/activity.service';
|
||||
import { ActivityTargetService } from './services/activity-target.service';
|
||||
|
||||
@Module({
|
||||
imports: [AbilityModule, PrismaModule],
|
||||
providers: [ActivityResolver, ActivityService, ActivityTargetService],
|
||||
exports: [ActivityService, ActivityTargetService],
|
||||
})
|
||||
export class ActivityModule {}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { ActivityResolver } from './activity.resolver';
|
||||
|
||||
describe('ActivityResolver', () => {
|
||||
let resolver: ActivityResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ActivityResolver,
|
||||
{
|
||||
provide: ActivityService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<ActivityResolver>(ActivityResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,156 +0,0 @@
|
||||
import { Resolver, Args, Mutation, Query } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import {
|
||||
PrismaSelector,
|
||||
PrismaSelect,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreateActivityAbilityHandler,
|
||||
DeleteActivityAbilityHandler,
|
||||
ReadActivityAbilityHandler,
|
||||
UpdateActivityAbilityHandler,
|
||||
} from 'src/ability/handlers/activity.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||
import { Activity } from 'src/core/@generated/activity/activity.model';
|
||||
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||
import { CreateOneActivityArgs } from 'src/core/@generated/activity/create-one-activity.args';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { UpdateOneActivityArgs } from 'src/core/@generated/activity/update-one-activity.args';
|
||||
import { FindManyActivityArgs } from 'src/core/@generated/activity/find-many-activity.args';
|
||||
import { DeleteManyActivityArgs } from 'src/core/@generated/activity/delete-many-activity.args';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Activity)
|
||||
export class ActivityResolver {
|
||||
constructor(private readonly activityService: ActivityService) {}
|
||||
|
||||
@Mutation(() => Activity, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateActivityAbilityHandler)
|
||||
async createOneActivity(
|
||||
@Args() args: CreateOneActivityArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Activity' })
|
||||
prismaSelect: PrismaSelect<'Activity'>,
|
||||
): Promise<Partial<Activity>> {
|
||||
const createdActivity = await this.activityService.create({
|
||||
data: {
|
||||
...args.data,
|
||||
...{ workspace: { connect: { id: workspace.id } } },
|
||||
activityTargets: args.data?.activityTargets?.createMany
|
||||
? {
|
||||
createMany: {
|
||||
data: args.data.activityTargets.createMany.data.map(
|
||||
(target) => ({ ...target, workspaceId: workspace.id }),
|
||||
),
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.ActivityCreateArgs);
|
||||
|
||||
return createdActivity;
|
||||
}
|
||||
|
||||
@Mutation(() => Activity, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateActivityAbilityHandler)
|
||||
async updateOneActivity(
|
||||
@Args() args: UpdateOneActivityArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Activity' })
|
||||
prismaSelect: PrismaSelect<'Activity'>,
|
||||
): Promise<Partial<Activity>> {
|
||||
// TODO: Do a proper check with recursion testing on args in a more generic place
|
||||
for (const key in args.data) {
|
||||
if (args.data[key]) {
|
||||
for (const subKey in args.data[key]) {
|
||||
if (JSON.stringify(args.data[key][subKey]) === '{}') {
|
||||
delete args.data[key][subKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(args.data[key]) === '{}') {
|
||||
delete args.data[key];
|
||||
}
|
||||
}
|
||||
const updatedActivity = await this.activityService.update({
|
||||
where: args.where,
|
||||
data: {
|
||||
...args.data,
|
||||
activityTargets: args.data?.activityTargets
|
||||
? {
|
||||
createMany: args.data.activityTargets.createMany
|
||||
? {
|
||||
data: args.data.activityTargets.createMany.data.map(
|
||||
(target) => ({
|
||||
...target,
|
||||
workspaceId: workspace.id,
|
||||
}),
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
deleteMany: args.data.activityTargets.deleteMany ?? undefined,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.ActivityUpdateArgs);
|
||||
|
||||
return updatedActivity;
|
||||
}
|
||||
|
||||
@Query(() => [Activity])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadActivityAbilityHandler)
|
||||
async findManyActivities(
|
||||
@Args() args: FindManyActivityArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'Activity' })
|
||||
prismaSelect: PrismaSelect<'Activity'>,
|
||||
): Promise<Partial<Activity>[]> {
|
||||
const result = await this.activityService.findMany({
|
||||
where: {
|
||||
...args.where,
|
||||
AND: [accessibleBy(ability).Activity],
|
||||
},
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteActivityAbilityHandler)
|
||||
async deleteManyActivities(
|
||||
@Args() args: DeleteManyActivityArgs,
|
||||
): Promise<AffectedRows> {
|
||||
return this.activityService.deleteMany({
|
||||
where: args.where,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { ActivityTargetService } from './activity-target.service';
|
||||
|
||||
describe('ActivityTargetService', () => {
|
||||
let service: ActivityTargetService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ActivityTargetService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ActivityTargetService>(ActivityTargetService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,40 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ActivityTargetService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.activityTarget.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.activityTarget.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.activityTarget.findUnique;
|
||||
findUniqueOrThrow =
|
||||
this.prismaService.client.activityTarget.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.activityTarget.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.activityTarget.create;
|
||||
createMany = this.prismaService.client.activityTarget.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.activityTarget.update;
|
||||
upsert = this.prismaService.client.activityTarget.upsert;
|
||||
updateMany = this.prismaService.client.activityTarget.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.activityTarget.delete;
|
||||
deleteMany = this.prismaService.client.activityTarget.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.activityTarget.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.activityTarget.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.activityTarget.groupBy;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { ActivityService } from './activity.service';
|
||||
|
||||
describe('ActivityService', () => {
|
||||
let service: ActivityService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ActivityService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ActivityService>(ActivityService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,39 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ActivityService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.activity.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.activity.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.activity.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.activity.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.activity.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.activity.create;
|
||||
createMany = this.prismaService.client.activity.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.activity.update;
|
||||
upsert = this.prismaService.client.activity.upsert;
|
||||
updateMany = this.prismaService.client.activity.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.activity.delete;
|
||||
deleteMany = this.prismaService.client.activity.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.activity.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.activity.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.activity.groupBy;
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import { Resolver, Mutation, Args } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { User, Workspace } from '@prisma/client';
|
||||
|
||||
import { OptionalJwtAuthGuard } from 'src/guards/optional-jwt.auth.guard';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
|
||||
import { AnalyticsService } from './analytics.service';
|
||||
import { Analytics } from './analytics.entity';
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { User, Workspace } from '@prisma/client';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
import { anonymize } from 'src/utils/anonymize';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
|
||||
import { CreateAnalyticsInput } from './dto/create-analytics.input';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
import { IsNotEmpty, IsString, IsObject } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
@ -10,7 +10,7 @@ export class CreateAnalyticsInput {
|
||||
@IsString()
|
||||
type: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { description: 'Event data in JSON format' })
|
||||
@Field(() => graphqlTypeJson, { description: 'Event data in JSON format' })
|
||||
@IsObject()
|
||||
data: JSON;
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { TokenService } from 'src/core/auth/services/token.service';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { ApiKeyResolver } from './api-key.resolver';
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
|
||||
@Module({
|
||||
imports: [AbilityModule, PrismaModule],
|
||||
providers: [ApiKeyResolver, ApiKeyService, TokenService, JwtService],
|
||||
})
|
||||
export class ApiKeyModule {}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
import { TokenService } from 'src/core/auth/services/token.service';
|
||||
|
||||
import { ApiKeyResolver } from './api-key.resolver';
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
|
||||
describe('ApiKeyResolver', () => {
|
||||
let resolver: ApiKeyResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ApiKeyResolver,
|
||||
{ provide: ApiKeyService, useValue: {} },
|
||||
{ provide: TokenService, useValue: {} },
|
||||
{ provide: JwtService, useValue: {} },
|
||||
{ provide: AbilityFactory, useValue: {} },
|
||||
],
|
||||
}).compile();
|
||||
resolver = module.get<ApiKeyResolver>(ApiKeyResolver);
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,97 +0,0 @@
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { NotFoundException, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { CreateOneApiKeyArgs } from 'src/core/@generated/api-key/create-one-api-key.args';
|
||||
import { ApiKey } from 'src/core/@generated/api-key/api-key.model';
|
||||
import { FindManyApiKeyArgs } from 'src/core/@generated/api-key/find-many-api-key.args';
|
||||
import { DeleteOneApiKeyArgs } from 'src/core/@generated/api-key/delete-one-api-key.args';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreateApiKeyAbilityHandler,
|
||||
UpdateApiKeyAbilityHandler,
|
||||
ReadApiKeyAbilityHandler,
|
||||
} from 'src/ability/handlers/api-key.ability-handler';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { ApiKeyToken } from 'src/core/auth/dto/token.entity';
|
||||
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => ApiKey)
|
||||
export class ApiKeyResolver {
|
||||
constructor(private readonly apiKeyService: ApiKeyService) {}
|
||||
|
||||
@Mutation(() => ApiKeyToken)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateApiKeyAbilityHandler)
|
||||
async createOneApiKey(
|
||||
@Args() args: CreateOneApiKeyArgs,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ApiKeyToken> {
|
||||
return await this.apiKeyService.generateApiKeyToken(
|
||||
workspaceId,
|
||||
args.data.name,
|
||||
args.data.expiresAt,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => ApiKeyToken)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateApiKeyAbilityHandler)
|
||||
async generateApiKeyV2Token(
|
||||
@Args()
|
||||
args: CreateOneApiKeyArgs,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<Pick<ApiKeyToken, 'token'> | undefined> {
|
||||
return await this.apiKeyService.generateApiKeyV2Token(
|
||||
workspaceId,
|
||||
args.data.id,
|
||||
args.data.expiresAt,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => ApiKey)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateApiKeyAbilityHandler)
|
||||
async revokeOneApiKey(
|
||||
@Args() args: DeleteOneApiKeyArgs,
|
||||
): Promise<Partial<ApiKey>> {
|
||||
const apiKeyToDelete = await this.apiKeyService.findFirst({
|
||||
where: { ...args.where },
|
||||
});
|
||||
if (!apiKeyToDelete) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return this.apiKeyService.update({
|
||||
where: args.where,
|
||||
data: {
|
||||
revokedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [ApiKey])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadApiKeyAbilityHandler)
|
||||
async findManyApiKey(
|
||||
@Args() args: FindManyApiKeyArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
) {
|
||||
const filterOptions = [
|
||||
accessibleBy(ability).WorkspaceMember,
|
||||
{ revokedAt: null },
|
||||
];
|
||||
if (args.where) filterOptions.push(args.where);
|
||||
return this.apiKeyService.findMany({
|
||||
...args,
|
||||
where: { AND: filterOptions },
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { ApiKeyToken } from 'src/core/auth/dto/token.entity';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyService {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
findFirst = this.prismaService.client.apiKey.findFirst;
|
||||
findUniqueOrThrow = this.prismaService.client.apiKey.findUniqueOrThrow;
|
||||
findMany = this.prismaService.client.apiKey.findMany;
|
||||
create = this.prismaService.client.apiKey.create;
|
||||
update = this.prismaService.client.apiKey.update;
|
||||
delete = this.prismaService.client.apiKey.delete;
|
||||
|
||||
async generateApiKeyV2Token(
|
||||
workspaceId: string,
|
||||
apiKeyId?: string,
|
||||
expiresAt?: Date | string,
|
||||
): Promise<Pick<ApiKeyToken, 'token'> | undefined> {
|
||||
if (!apiKeyId) {
|
||||
return;
|
||||
}
|
||||
const jwtPayload = {
|
||||
sub: workspaceId,
|
||||
};
|
||||
const secret = this.environmentService.getAccessTokenSecret();
|
||||
let expiresIn: string | number;
|
||||
if (expiresAt) {
|
||||
expiresIn = Math.floor(
|
||||
(new Date(expiresAt).getTime() - new Date().getTime()) / 1000,
|
||||
);
|
||||
} else {
|
||||
expiresIn = this.environmentService.getApiTokenExpiresIn();
|
||||
}
|
||||
const token = this.jwtService.sign(jwtPayload, {
|
||||
secret,
|
||||
expiresIn,
|
||||
jwtid: apiKeyId,
|
||||
});
|
||||
return { token };
|
||||
}
|
||||
|
||||
async generateApiKeyToken(
|
||||
workspaceId: string,
|
||||
name: string,
|
||||
expiresAt?: Date | string,
|
||||
): Promise<ApiKeyToken> {
|
||||
const secret = this.environmentService.getAccessTokenSecret();
|
||||
let expiresIn: string | number;
|
||||
const now = new Date().getTime();
|
||||
if (expiresAt) {
|
||||
expiresIn = Math.floor((new Date(expiresAt).getTime() - now) / 1000);
|
||||
} else {
|
||||
expiresIn = this.environmentService.getApiTokenExpiresIn();
|
||||
}
|
||||
assert(expiresIn, '', InternalServerErrorException);
|
||||
const jwtPayload = {
|
||||
sub: workspaceId,
|
||||
};
|
||||
const newApiKey = await this.prismaService.client.apiKey.create({
|
||||
data: {
|
||||
expiresAt: expiresAt,
|
||||
name: name,
|
||||
workspaceId: workspaceId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
...newApiKey,
|
||||
token: this.jwtService.sign(jwtPayload, {
|
||||
secret,
|
||||
expiresIn,
|
||||
jwtid: newApiKey.id,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { AttachmentResolver } from './resolvers/attachment.resolver';
|
||||
import { AttachmentService } from './services/attachment.service';
|
||||
|
||||
@Module({
|
||||
imports: [AbilityModule, PrismaModule],
|
||||
providers: [AttachmentService, AttachmentResolver, FileUploadService],
|
||||
exports: [AttachmentService],
|
||||
})
|
||||
export class AttachmentModule {}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { AttachmentService } from 'src/core/attachment/services/attachment.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { AttachmentResolver } from './attachment.resolver';
|
||||
|
||||
describe('AttachmentResolver', () => {
|
||||
let resolver: AttachmentResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AttachmentResolver,
|
||||
{
|
||||
provide: FileUploadService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AttachmentService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<AttachmentResolver>(AttachmentResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,72 +0,0 @@
|
||||
import { Resolver, Args, Mutation } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { User, Workspace } from '@prisma/client';
|
||||
import { GraphQLUpload, FileUpload } from 'graphql-upload';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { AttachmentService } from 'src/core/attachment/services/attachment.service';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { Attachment } from 'src/core/@generated/attachment/attachment.model';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CreateAttachmentAbilityHandler } from 'src/ability/handlers/attachment.ability-handler';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Attachment)
|
||||
@Resolver()
|
||||
export class AttachmentResolver {
|
||||
constructor(
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
private readonly attachmentService: AttachmentService,
|
||||
) {}
|
||||
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateAttachmentAbilityHandler)
|
||||
@Mutation(() => String)
|
||||
async uploadAttachment(
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
@Args('activityId') activityId?: string,
|
||||
@Args('companyId') companyId?: string,
|
||||
@Args('personId') personId?: string,
|
||||
): Promise<string> {
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
|
||||
const { path } = await this.fileUploadService.uploadFile({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder: FileFolder.Attachment,
|
||||
});
|
||||
|
||||
await this.attachmentService.create({
|
||||
data: {
|
||||
id: uuidV4(),
|
||||
fullPath: path,
|
||||
type: this.attachmentService.getFileTypeFromFileName(filename),
|
||||
name: filename,
|
||||
activityId,
|
||||
companyId,
|
||||
personId,
|
||||
authorId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
fullPath: true,
|
||||
},
|
||||
});
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { AttachmentService } from './attachment.service';
|
||||
|
||||
describe('AttachmentService', () => {
|
||||
let service: AttachmentService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AttachmentService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AttachmentService>(AttachmentService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,83 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { AttachmentType } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.attachment.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.attachment.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.attachment.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.attachment.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.attachment.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.attachment.create;
|
||||
createMany = this.prismaService.client.attachment.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.attachment.update;
|
||||
upsert = this.prismaService.client.attachment.upsert;
|
||||
updateMany = this.prismaService.client.attachment.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.attachment.delete;
|
||||
deleteMany = this.prismaService.client.attachment.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.attachment.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.attachment.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.attachment.groupBy;
|
||||
|
||||
getFileTypeFromFileName(fileName: string): AttachmentType {
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
case 'mp4':
|
||||
case 'avi':
|
||||
case 'mov':
|
||||
return AttachmentType.Video;
|
||||
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'ogg':
|
||||
return AttachmentType.Audio;
|
||||
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
return AttachmentType.Image;
|
||||
|
||||
case 'txt':
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
case 'pdf':
|
||||
return AttachmentType.TextDocument;
|
||||
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
case 'csv':
|
||||
return AttachmentType.Spreadsheet;
|
||||
|
||||
case 'zip':
|
||||
case 'rar':
|
||||
case 'tar':
|
||||
case '7z':
|
||||
return AttachmentType.Archive;
|
||||
|
||||
default:
|
||||
return AttachmentType.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,21 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { UserModule } from 'src/core/user/user.module';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
||||
import { FileModule } from 'src/core/file/file.module';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { UserModule } from 'src/core/user/user.module';
|
||||
import { WorkspaceManagerModule } from 'src/workspace/workspace-manager/workspace-manager.module';
|
||||
|
||||
import config from '../../../ormconfig';
|
||||
|
||||
import { AuthResolver } from './auth.resolver';
|
||||
|
||||
@ -28,15 +38,22 @@ const jwtModule = JwtModule.registerAsync({
|
||||
});
|
||||
|
||||
@Module({
|
||||
imports: [jwtModule, UserModule, WorkspaceModule, FileModule],
|
||||
controllers: [GoogleAuthController, VerifyAuthController],
|
||||
providers: [
|
||||
AuthService,
|
||||
TokenService,
|
||||
JwtAuthStrategy,
|
||||
PrismaService,
|
||||
AuthResolver,
|
||||
imports: [
|
||||
jwtModule,
|
||||
FileModule,
|
||||
DataSourceModule,
|
||||
UserModule,
|
||||
WorkspaceManagerModule,
|
||||
TypeOrmModule.forRoot(config),
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace, User, RefreshToken]),
|
||||
TypeORMModule,
|
||||
],
|
||||
}),
|
||||
],
|
||||
controllers: [GoogleAuthController, VerifyAuthController],
|
||||
providers: [AuthService, TokenService, JwtAuthStrategy, AuthResolver],
|
||||
exports: [jwtModule],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@ -4,21 +4,19 @@ import {
|
||||
ForbiddenException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { User } from 'src/core/@generated/user/user.model';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { ApiKeyTokenInput } from 'src/core/auth/dto/api-key-token.input';
|
||||
|
||||
import { AuthTokens } from './dto/token.entity';
|
||||
import { ApiKeyToken, AuthTokens } from './dto/token.entity';
|
||||
import { TokenService } from './services/token.service';
|
||||
import { RefreshTokenInput } from './dto/refresh-token.input';
|
||||
import { Verify } from './dto/verify.entity';
|
||||
@ -36,7 +34,8 @@ import { ImpersonateInput } from './dto/impersonate.input';
|
||||
@Resolver()
|
||||
export class AuthResolver {
|
||||
constructor(
|
||||
private workspaceService: WorkspaceService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private authService: AuthService,
|
||||
private tokenService: TokenService,
|
||||
) {}
|
||||
@ -64,10 +63,8 @@ export class AuthResolver {
|
||||
async findWorkspaceFromInviteHash(
|
||||
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
||||
) {
|
||||
return await this.workspaceService.findFirst({
|
||||
where: {
|
||||
inviteHash: workspaceInviteHashValidInput.inviteHash,
|
||||
},
|
||||
return await this.workspaceRepository.findOneBy({
|
||||
inviteHash: workspaceInviteHashValidInput.inviteHash,
|
||||
});
|
||||
}
|
||||
|
||||
@ -88,21 +85,12 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => Verify)
|
||||
async verify(
|
||||
@Args() verifyInput: VerifyInput,
|
||||
@PrismaSelector({
|
||||
modelName: 'User',
|
||||
defaultFields: { User: { id: true } },
|
||||
})
|
||||
prismaSelect: PrismaSelect<'User'>,
|
||||
): Promise<Verify> {
|
||||
async verify(@Args() verifyInput: VerifyInput): Promise<Verify> {
|
||||
const email = await this.tokenService.verifyLoginToken(
|
||||
verifyInput.loginToken,
|
||||
);
|
||||
const select = prismaSelect.valueOf('user') as Prisma.UserSelect & {
|
||||
id: true;
|
||||
};
|
||||
const result = await this.authService.verify(email, select);
|
||||
|
||||
const result = await this.authService.verify(email);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -125,22 +113,24 @@ export class AuthResolver {
|
||||
async impersonate(
|
||||
@Args() impersonateInput: ImpersonateInput,
|
||||
@AuthUser() user: User,
|
||||
@PrismaSelector({
|
||||
modelName: 'User',
|
||||
defaultFields: {
|
||||
User: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
prismaSelect: PrismaSelect<'User'>,
|
||||
): Promise<Verify> {
|
||||
// Check if user can impersonate
|
||||
assert(user.canImpersonate, 'User cannot impersonate', ForbiddenException);
|
||||
const select = prismaSelect.valueOf('user') as Prisma.UserSelect & {
|
||||
id: true;
|
||||
};
|
||||
|
||||
return this.authService.impersonate(impersonateInput.userId, select);
|
||||
return this.authService.impersonate(impersonateInput.userId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Mutation(() => ApiKeyToken)
|
||||
async generateApiKeyToken(
|
||||
@Args() args: ApiKeyTokenInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ApiKeyToken | undefined> {
|
||||
console.log('toto');
|
||||
return await this.tokenService.generateApiKeyToken(
|
||||
workspaceId,
|
||||
args.apiKeyId,
|
||||
args.expiresAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Response } from 'express';
|
||||
import FileType from 'file-type';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { GoogleRequest } from 'src/core/auth/strategies/google.auth.strategy';
|
||||
import { UserService } from 'src/core/user/user.service';
|
||||
import { TokenService } from 'src/core/auth/services/token.service';
|
||||
import { GoogleProviderEnabledGuard } from 'src/core/auth/guards/google-provider-enabled.guard';
|
||||
import { GoogleOauthGuard } from 'src/core/auth/guards/google-oauth.guard';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { AuthService } from 'src/core/auth/services/auth.service';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
|
||||
@Controller('auth/google')
|
||||
export class GoogleAuthController {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly userService: UserService,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly authService: AuthService,
|
||||
@InjectRepository(Workspace)
|
||||
@InjectRepository(User, 'metadata')
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ -39,65 +39,29 @@ export class GoogleAuthController {
|
||||
const { firstName, lastName, email, picture, workspaceInviteHash } =
|
||||
req.user;
|
||||
|
||||
let workspaceId: string | undefined = undefined;
|
||||
if (workspaceInviteHash) {
|
||||
const workspace = await this.workspaceService.findFirst({
|
||||
where: {
|
||||
inviteHash: workspaceInviteHash,
|
||||
},
|
||||
});
|
||||
const mainDataSource = await this.typeORMService.getMainDataSource();
|
||||
|
||||
if (!workspace) {
|
||||
return res.redirect(
|
||||
`${this.environmentService.getFrontAuthCallbackUrl()}`,
|
||||
);
|
||||
}
|
||||
const existingUser = await mainDataSource
|
||||
.getRepository(User)
|
||||
.findOneBy({ email: email });
|
||||
|
||||
workspaceId = workspace.id;
|
||||
if (existingUser) {
|
||||
const loginToken = await this.tokenService.generateLoginToken(
|
||||
existingUser.email,
|
||||
);
|
||||
|
||||
return res.redirect(
|
||||
this.tokenService.computeRedirectURI(loginToken.token),
|
||||
);
|
||||
}
|
||||
|
||||
let user = await this.userService.createUser(
|
||||
{
|
||||
data: {
|
||||
email,
|
||||
firstName: firstName ?? '',
|
||||
lastName: lastName ?? '',
|
||||
locale: 'en',
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!user.avatarUrl) {
|
||||
let imagePath: string | undefined = undefined;
|
||||
|
||||
if (picture) {
|
||||
// Get image buffer from url
|
||||
const buffer = await getImageBufferFromUrl(picture);
|
||||
|
||||
// Extract mimetype and extension from buffer
|
||||
const type = await FileType.fromBuffer(buffer);
|
||||
|
||||
// Upload image
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename: `${uuidV4()}.${type?.ext}`,
|
||||
mimeType: type?.mime,
|
||||
fileFolder: FileFolder.ProfilePicture,
|
||||
});
|
||||
|
||||
imagePath = paths[0];
|
||||
}
|
||||
|
||||
user = await this.userService.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
avatarUrl: imagePath,
|
||||
},
|
||||
});
|
||||
}
|
||||
const user = await this.authService.signUp({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
picture,
|
||||
workspaceInviteHash,
|
||||
});
|
||||
|
||||
const loginToken = await this.tokenService.generateLoginToken(user.email);
|
||||
|
||||
|
||||
@ -17,13 +17,7 @@ export class VerifyAuthController {
|
||||
const email = await this.tokenService.verifyLoginToken(
|
||||
verifyInput.loginToken,
|
||||
);
|
||||
const result = await this.authService.verify(email, {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
email: true,
|
||||
emailVerified: true,
|
||||
});
|
||||
const result = await this.authService.verify(email);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
15
server/src/core/auth/dto/api-key-token.input.ts
Normal file
15
server/src/core/auth/dto/api-key-token.input.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class ApiKeyTokenInput {
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
apiKeyId: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
expiresAt: string;
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { ApiKey } from 'src/core/@generated/api-key/api-key.model';
|
||||
|
||||
@ObjectType()
|
||||
export class AuthToken {
|
||||
@Field(() => String)
|
||||
@ -12,7 +10,7 @@ export class AuthToken {
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ApiKeyToken extends ApiKey {
|
||||
export class ApiKeyToken {
|
||||
@Field(() => String)
|
||||
token: string;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { User } from 'src/core/@generated/user/user.model';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
|
||||
import { AuthTokens } from './token.entity';
|
||||
|
||||
|
||||
@ -4,11 +4,15 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
import FileType from 'file-type';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { ChallengeInput } from 'src/core/auth/dto/challenge.input';
|
||||
import { UserService } from 'src/core/user/user.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import {
|
||||
PASSWORD_REGEX,
|
||||
@ -17,9 +21,13 @@ import {
|
||||
} from 'src/core/auth/auth.util';
|
||||
import { Verify } from 'src/core/auth/dto/verify.entity';
|
||||
import { UserExists } from 'src/core/auth/dto/user-exists.entity';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { WorkspaceInviteHashValid } from 'src/core/auth/dto/workspace-invite-hash-valid.entity';
|
||||
import { SignUpInput } from 'src/core/auth/dto/sign-up.input';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { UserService } from 'src/core/user/services/user.service';
|
||||
import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspace-manager.service';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
|
||||
import { TokenService } from './token.service';
|
||||
|
||||
@ -34,14 +42,17 @@ export class AuthService {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly userService: UserService,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly workspaceManagerService: WorkspaceManagerService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
async challenge(challengeInput: ChallengeInput) {
|
||||
const user = await this.userService.findUnique({
|
||||
where: {
|
||||
email: challengeInput.email,
|
||||
},
|
||||
const user = await this.userRepository.findOneBy({
|
||||
email: challengeInput.email,
|
||||
});
|
||||
|
||||
assert(user, "This user doesn't exist", NotFoundException);
|
||||
@ -57,24 +68,40 @@ export class AuthService {
|
||||
return user;
|
||||
}
|
||||
|
||||
async signUp(signUpInput: SignUpInput) {
|
||||
const existingUser = await this.userService.findUnique({
|
||||
where: {
|
||||
email: signUpInput.email,
|
||||
},
|
||||
async signUp({
|
||||
email,
|
||||
password,
|
||||
workspaceInviteHash,
|
||||
firstName,
|
||||
lastName,
|
||||
picture,
|
||||
}: {
|
||||
email: string;
|
||||
password?: string;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
workspaceInviteHash?: string | null;
|
||||
picture?: string | null;
|
||||
}) {
|
||||
if (!firstName) firstName = '';
|
||||
if (!lastName) lastName = '';
|
||||
|
||||
const existingUser = await this.userRepository.findOneBy({
|
||||
email: email,
|
||||
});
|
||||
assert(!existingUser, 'This user already exists', ForbiddenException);
|
||||
|
||||
const isPasswordValid = PASSWORD_REGEX.test(signUpInput.password);
|
||||
assert(isPasswordValid, 'Password too weak', BadRequestException);
|
||||
if (password) {
|
||||
const isPasswordValid = PASSWORD_REGEX.test(password);
|
||||
assert(isPasswordValid, 'Password too weak', BadRequestException);
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(signUpInput.password);
|
||||
const passwordHash = password ? await hashPassword(password) : undefined;
|
||||
let workspace: Workspace | null;
|
||||
|
||||
if (signUpInput.workspaceInviteHash) {
|
||||
const workspace = await this.workspaceService.findFirst({
|
||||
where: {
|
||||
inviteHash: signUpInput.workspaceInviteHash,
|
||||
},
|
||||
if (workspaceInviteHash) {
|
||||
workspace = await this.workspaceRepository.findOneBy({
|
||||
inviteHash: workspaceInviteHash,
|
||||
});
|
||||
|
||||
assert(
|
||||
@ -82,44 +109,59 @@ export class AuthService {
|
||||
'This workspace inviteHash is invalid',
|
||||
ForbiddenException,
|
||||
);
|
||||
|
||||
return await this.userService.createUser(
|
||||
{
|
||||
data: {
|
||||
email: signUpInput.email,
|
||||
passwordHash,
|
||||
},
|
||||
} as Prisma.UserCreateArgs,
|
||||
workspace.id,
|
||||
);
|
||||
} else {
|
||||
const workspaceToCreate = this.workspaceRepository.create({
|
||||
displayName: '',
|
||||
domainName: '',
|
||||
inviteHash: v4(),
|
||||
});
|
||||
workspace = await this.workspaceRepository.save(workspaceToCreate);
|
||||
await this.workspaceManagerService.init(workspace.id);
|
||||
}
|
||||
|
||||
return await this.userService.createUser({
|
||||
data: {
|
||||
email: signUpInput.email,
|
||||
passwordHash,
|
||||
locale: 'en',
|
||||
},
|
||||
} as Prisma.UserCreateArgs);
|
||||
const userToCreate = this.userRepository.create({
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
canImpersonate: false,
|
||||
passwordHash,
|
||||
defaultWorkspace: workspace,
|
||||
});
|
||||
const user = await this.userRepository.save(userToCreate);
|
||||
let imagePath: string | undefined = undefined;
|
||||
|
||||
if (picture) {
|
||||
const buffer = await getImageBufferFromUrl(picture);
|
||||
|
||||
const type = await FileType.fromBuffer(buffer);
|
||||
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename: `${v4()}.${type?.ext}`,
|
||||
mimeType: type?.mime,
|
||||
fileFolder: FileFolder.ProfilePicture,
|
||||
});
|
||||
|
||||
imagePath = paths[0];
|
||||
}
|
||||
await this.userService.createWorkspaceMember(user, imagePath);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async verify(
|
||||
email: string,
|
||||
select: Prisma.UserSelect & {
|
||||
id: true;
|
||||
},
|
||||
): Promise<Verify> {
|
||||
const user = await this.userService.findUnique({
|
||||
async verify(email: string): Promise<Verify> {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
select,
|
||||
relations: ['defaultWorkspace'],
|
||||
});
|
||||
|
||||
assert(user, "This user doesn't exist", NotFoundException);
|
||||
|
||||
// passwordHash is hidden for security reasons
|
||||
user.passwordHash = '';
|
||||
user.workspaceMember = await this.userService.loadWorkspaceMember(user);
|
||||
|
||||
const accessToken = await this.tokenService.generateAccessToken(user.id);
|
||||
const refreshToken = await this.tokenService.generateRefreshToken(user.id);
|
||||
@ -134,10 +176,8 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async checkUserExists(email: string): Promise<UserExists> {
|
||||
const user = await this.userService.findUnique({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
const user = await this.userRepository.findOneBy({
|
||||
email,
|
||||
});
|
||||
|
||||
return { exists: !!user };
|
||||
@ -146,26 +186,16 @@ export class AuthService {
|
||||
async checkWorkspaceInviteHashIsValid(
|
||||
inviteHash: string,
|
||||
): Promise<WorkspaceInviteHashValid> {
|
||||
const workspace = await this.workspaceService.findFirst({
|
||||
where: {
|
||||
inviteHash,
|
||||
},
|
||||
const workspace = await this.workspaceRepository.findOneBy({
|
||||
inviteHash,
|
||||
});
|
||||
|
||||
return { isValid: !!workspace };
|
||||
}
|
||||
|
||||
async impersonate(
|
||||
userId: string,
|
||||
select: Prisma.UserSelect & {
|
||||
id: true;
|
||||
},
|
||||
) {
|
||||
const user = await this.userService.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select,
|
||||
async impersonate(userId: string) {
|
||||
const user = await this.userRepository.findOneBy({
|
||||
id: userId,
|
||||
});
|
||||
|
||||
assert(user, "This user doesn't exist", NotFoundException);
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
|
||||
import { TokenService } from './token.service';
|
||||
@ -22,10 +20,6 @@ describe('TokenService', () => {
|
||||
provide: EnvironmentService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@ -7,23 +7,29 @@ import {
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { TokenExpiredError } from 'jsonwebtoken';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { JwtPayload } from 'src/core/auth/strategies/jwt.auth.strategy';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { AuthToken } from 'src/core/auth/dto/token.entity';
|
||||
import { ApiKeyToken, AuthToken } from 'src/core/auth/dto/token.entity';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TokenService {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly prismaService: PrismaService,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(RefreshToken)
|
||||
private readonly refreshTokenRepository: Repository<RefreshToken>,
|
||||
) {}
|
||||
|
||||
async generateAccessToken(userId: string): Promise<AuthToken> {
|
||||
@ -31,23 +37,26 @@ export class TokenService {
|
||||
assert(expiresIn, '', InternalServerErrorException);
|
||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||
|
||||
const user = await this.prismaService.client.user.findUnique({
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: userId },
|
||||
relations: ['defaultWorkspace'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User is not found');
|
||||
}
|
||||
|
||||
if (!user.defaultWorkspaceId) {
|
||||
if (!user.defaultWorkspace) {
|
||||
throw new NotFoundException('User does not have a default workspace');
|
||||
}
|
||||
|
||||
const jwtPayload: JwtPayload = {
|
||||
sub: user.id,
|
||||
workspaceId: user.defaultWorkspaceId,
|
||||
workspaceId: user.defaultWorkspace.id,
|
||||
};
|
||||
|
||||
console.log(jwtPayload);
|
||||
|
||||
return {
|
||||
token: this.jwtService.sign(jwtPayload),
|
||||
expiresAt,
|
||||
@ -68,9 +77,13 @@ export class TokenService {
|
||||
sub: userId,
|
||||
};
|
||||
|
||||
const refreshToken = await this.prismaService.client.refreshToken.create({
|
||||
data: refreshTokenPayload,
|
||||
});
|
||||
const refreshToken =
|
||||
this.refreshTokenRepository.create(refreshTokenPayload);
|
||||
console.log(refreshToken);
|
||||
|
||||
await this.refreshTokenRepository.save(refreshToken);
|
||||
|
||||
console.log('toto');
|
||||
|
||||
return {
|
||||
token: this.jwtService.sign(jwtPayload, {
|
||||
@ -101,6 +114,34 @@ export class TokenService {
|
||||
};
|
||||
}
|
||||
|
||||
async generateApiKeyToken(
|
||||
workspaceId: string,
|
||||
apiKeyId?: string,
|
||||
expiresAt?: Date | string,
|
||||
): Promise<Pick<ApiKeyToken, 'token'> | undefined> {
|
||||
if (!apiKeyId) {
|
||||
return;
|
||||
}
|
||||
const jwtPayload = {
|
||||
sub: workspaceId,
|
||||
};
|
||||
const secret = this.environmentService.getAccessTokenSecret();
|
||||
let expiresIn: string | number;
|
||||
if (expiresAt) {
|
||||
expiresIn = Math.floor(
|
||||
(new Date(expiresAt).getTime() - new Date().getTime()) / 1000,
|
||||
);
|
||||
} else {
|
||||
expiresIn = this.environmentService.getApiTokenExpiresIn();
|
||||
}
|
||||
const token = this.jwtService.sign(jwtPayload, {
|
||||
secret,
|
||||
expiresIn,
|
||||
jwtid: apiKeyId,
|
||||
});
|
||||
return { token };
|
||||
}
|
||||
|
||||
async verifyLoginToken(loginToken: string): Promise<string> {
|
||||
const loginTokenSecret = this.environmentService.getLoginTokenSecret();
|
||||
|
||||
@ -120,19 +161,14 @@ export class TokenService {
|
||||
UnprocessableEntityException,
|
||||
);
|
||||
|
||||
const token = await this.prismaService.client.refreshToken.findUnique({
|
||||
where: { id: jwtPayload.jti },
|
||||
const token = await this.refreshTokenRepository.findOneBy({
|
||||
id: jwtPayload.jti,
|
||||
});
|
||||
|
||||
assert(token, "This refresh token doesn't exist", NotFoundException);
|
||||
|
||||
const user = await this.prismaService.client.user.findUnique({
|
||||
where: {
|
||||
id: jwtPayload.sub,
|
||||
},
|
||||
include: {
|
||||
refreshTokens: true,
|
||||
},
|
||||
const user = await this.userRepository.findOneBy({
|
||||
id: jwtPayload.sub,
|
||||
});
|
||||
|
||||
assert(user, 'User not found', NotFoundException);
|
||||
@ -143,16 +179,17 @@ export class TokenService {
|
||||
token.revokedAt.getTime() <= Date.now() - ms(coolDown)
|
||||
) {
|
||||
// Revoke all user refresh tokens
|
||||
await this.prismaService.client.refreshToken.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: user.refreshTokens.map(({ id }) => id),
|
||||
},
|
||||
},
|
||||
data: {
|
||||
revokedAt: new Date(),
|
||||
},
|
||||
});
|
||||
await Promise.all(
|
||||
user.refreshTokens.map(
|
||||
async ({ id }) =>
|
||||
await this.refreshTokenRepository.update(
|
||||
{ id },
|
||||
{
|
||||
revokedAt: new Date(),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
throw new ForbiddenException(
|
||||
'Suspicious activity detected, this refresh token has been revoked. All tokens has been revoked.',
|
||||
@ -172,14 +209,14 @@ export class TokenService {
|
||||
} = await this.verifyRefreshToken(token);
|
||||
|
||||
// Revoke old refresh token
|
||||
await this.prismaService.client.refreshToken.update({
|
||||
where: {
|
||||
await this.refreshTokenRepository.update(
|
||||
{
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
{
|
||||
revokedAt: new Date(),
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const accessToken = await this.generateAccessToken(user.id);
|
||||
const refreshToken = await this.generateRefreshToken(user.id);
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import {
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Strategy, ExtractJwt } from 'passport-jwt';
|
||||
import { User, Workspace } from '@prisma/client';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
|
||||
export type JwtPayload = { sub: string; workspaceId: string; jti?: string };
|
||||
export type PassportUser = { user?: User; workspace: Workspace };
|
||||
@ -19,7 +16,10 @@ export type PassportUser = { user?: User; workspace: Workspace };
|
||||
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly prismaService: PrismaService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
@ -29,26 +29,30 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload): Promise<PassportUser> {
|
||||
const workspace = await this.prismaService.client.workspace.findUnique({
|
||||
where: { id: payload.workspaceId ?? payload.sub },
|
||||
const workspace = await this.workspaceRepository.findOneBy({
|
||||
id: payload.workspaceId ?? payload.sub,
|
||||
});
|
||||
if (!workspace) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
if (payload.jti) {
|
||||
// If apiKey has been deleted or revoked, we throw an error
|
||||
const apiKey = await this.prismaService.client.apiKey.findUniqueOrThrow({
|
||||
where: { id: payload.jti },
|
||||
});
|
||||
assert(!apiKey.revokedAt, 'This API Key is revoked', ForbiddenException);
|
||||
// const apiKey = await this.prismaService.client.apiKey.findUniqueOrThrow({
|
||||
// where: { id: payload.jti },
|
||||
// });
|
||||
// assert(!apiKey.revokedAt, 'This API Key is revoked', ForbiddenException);
|
||||
}
|
||||
|
||||
const user = payload.workspaceId
|
||||
? await this.prismaService.client.user.findUniqueOrThrow({
|
||||
where: { id: payload.sub },
|
||||
? await this.userRepository.findOneBy({
|
||||
id: payload.sub,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
return { user, workspace };
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { CommentService } from './comment.service';
|
||||
import { CommentResolver } from './comment.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [AbilityModule, PrismaModule],
|
||||
providers: [CommentService, CommentResolver],
|
||||
exports: [CommentService],
|
||||
})
|
||||
export class CommentModule {}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { CommentService } from 'src/core/comment/comment.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { CommentResolver } from './comment.resolver';
|
||||
|
||||
describe('CommentResolver', () => {
|
||||
let resolver: CommentResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
CommentResolver,
|
||||
{
|
||||
provide: CommentService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<CommentResolver>(CommentResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,47 +0,0 @@
|
||||
import { Resolver, Args, Mutation } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { CreateOneCommentArgs } from 'src/core/@generated/comment/create-one-comment.args';
|
||||
import { Comment } from 'src/core/@generated/comment/comment.model';
|
||||
import { CommentService } from 'src/core/comment/comment.service';
|
||||
import {
|
||||
PrismaSelector,
|
||||
PrismaSelect,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import { CreateCommentAbilityHandler } from 'src/ability/handlers/comment.ability-handler';
|
||||
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||
import { User } from 'src/core/@generated/user/user.model';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Comment)
|
||||
export class CommentResolver {
|
||||
constructor(private readonly commentService: CommentService) {}
|
||||
|
||||
@Mutation(() => Comment, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateCommentAbilityHandler)
|
||||
async createOneComment(
|
||||
@Args() args: CreateOneCommentArgs,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Comment' })
|
||||
prismaSelect: PrismaSelect<'Comment'>,
|
||||
): Promise<Partial<Comment>> {
|
||||
return this.commentService.create({
|
||||
data: {
|
||||
...args.data,
|
||||
...{ workspace: { connect: { id: workspace.id } } },
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.CommentCreateArgs);
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { CommentService } from './comment.service';
|
||||
|
||||
describe('CommentService', () => {
|
||||
let service: CommentService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
CommentService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CommentService>(CommentService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,39 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class CommentService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.comment.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.comment.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.comment.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.comment.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.comment.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.comment.create;
|
||||
createMany = this.prismaService.client.comment.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.comment.update;
|
||||
upsert = this.prismaService.client.comment.upsert;
|
||||
updateMany = this.prismaService.client.comment.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.comment.delete;
|
||||
deleteMany = this.prismaService.client.comment.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.comment.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.comment.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.comment.groupBy;
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { CommentService } from 'src/core/comment/comment.service';
|
||||
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||
|
||||
import { CompanyRelationsResolver } from './company-relations.resolver';
|
||||
import { CompanyService } from './company.service';
|
||||
|
||||
describe('CompanyRelationsResolver', () => {
|
||||
let resolver: CompanyRelationsResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
CompanyRelationsResolver,
|
||||
{
|
||||
provide: CompanyService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: ActivityService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: CommentService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<CompanyRelationsResolver>(CompanyRelationsResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,76 +0,0 @@
|
||||
import { Resolver, ResolveField, Root, Int } from '@nestjs/graphql';
|
||||
|
||||
import { Comment } from 'src/core/@generated/comment/comment.model';
|
||||
import { Company } from 'src/core/@generated/company/company.model';
|
||||
import { CommentService } from 'src/core/comment/comment.service';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||
import { Activity } from 'src/core/@generated/activity/activity.model';
|
||||
|
||||
@Resolver(() => Company)
|
||||
export class CompanyRelationsResolver {
|
||||
constructor(
|
||||
private readonly activityService: ActivityService,
|
||||
private readonly commentService: CommentService,
|
||||
) {}
|
||||
|
||||
@ResolveField(() => [Activity], {
|
||||
nullable: false,
|
||||
})
|
||||
async activities(
|
||||
@Root() company: Company,
|
||||
@PrismaSelector({ modelName: 'Activity' })
|
||||
prismaSelect: PrismaSelect<'Activity'>,
|
||||
): Promise<Partial<Activity>[]> {
|
||||
return this.activityService.findMany({
|
||||
where: {
|
||||
activityTargets: {
|
||||
some: {
|
||||
companyId: company.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => [Comment], {
|
||||
nullable: false,
|
||||
})
|
||||
async comments(
|
||||
@Root() company: Company,
|
||||
@PrismaSelector({ modelName: 'Comment' })
|
||||
prismaSelect: PrismaSelect<'Comment'>,
|
||||
): Promise<Partial<Comment>[]> {
|
||||
return this.commentService.findMany({
|
||||
where: {
|
||||
activity: {
|
||||
activityTargets: {
|
||||
some: {
|
||||
companyId: company.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => Int, {
|
||||
nullable: false,
|
||||
})
|
||||
async _activityCount(@Root() company: Company): Promise<number> {
|
||||
return this.activityService.count({
|
||||
where: {
|
||||
activityTargets: {
|
||||
some: {
|
||||
companyId: company.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CommentModule } from 'src/core/comment/comment.module';
|
||||
import { ActivityModule } from 'src/core/activity/activity.module';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { CompanyService } from './company.service';
|
||||
import { CompanyResolver } from './company.resolver';
|
||||
import { CompanyRelationsResolver } from './company-relations.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [CommentModule, ActivityModule, AbilityModule, PrismaModule],
|
||||
providers: [CompanyService, CompanyResolver, CompanyRelationsResolver],
|
||||
exports: [CompanyService],
|
||||
})
|
||||
export class CompanyModule {}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { CompanyService } from './company.service';
|
||||
import { CompanyResolver } from './company.resolver';
|
||||
|
||||
describe('CompanyResolver', () => {
|
||||
let resolver: CompanyResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
CompanyResolver,
|
||||
{
|
||||
provide: CompanyService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<CompanyResolver>(CompanyResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,145 +0,0 @@
|
||||
import { Resolver, Query, Args, Mutation } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { Company } from 'src/core/@generated/company/company.model';
|
||||
import { FindManyCompanyArgs } from 'src/core/@generated/company/find-many-company.args';
|
||||
import { UpdateOneCompanyArgs } from 'src/core/@generated/company/update-one-company.args';
|
||||
import { CreateOneCompanyArgs } from 'src/core/@generated/company/create-one-company.args';
|
||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||
import { DeleteManyCompanyArgs } from 'src/core/@generated/company/delete-many-company.args';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreateCompanyAbilityHandler,
|
||||
DeleteCompanyAbilityHandler,
|
||||
ReadOneCompanyAbilityHandler,
|
||||
UpdateCompanyAbilityHandler,
|
||||
} from 'src/ability/handlers/company.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { FindUniqueCompanyArgs } from 'src/core/@generated/company/find-unique-company.args';
|
||||
import { CreateManyCompanyArgs } from 'src/core/@generated/company/create-many-company.args';
|
||||
|
||||
import { CompanyService } from './company.service';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Company)
|
||||
export class CompanyResolver {
|
||||
constructor(private readonly companyService: CompanyService) {}
|
||||
|
||||
@Query(() => [Company])
|
||||
@UseGuards(AbilityGuard)
|
||||
async findManyCompany(
|
||||
@Args() args: FindManyCompanyArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'Company' })
|
||||
prismaSelect: PrismaSelect<'Company'>,
|
||||
): Promise<Partial<Company>[]> {
|
||||
return this.companyService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).Company],
|
||||
}
|
||||
: accessibleBy(ability).Company,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => Company)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadOneCompanyAbilityHandler)
|
||||
async findUniqueCompany(
|
||||
@Args() args: FindUniqueCompanyArgs,
|
||||
@PrismaSelector({ modelName: 'Company' })
|
||||
prismaSelect: PrismaSelect<'Company'>,
|
||||
): Promise<Partial<Company>> {
|
||||
const company = this.companyService.findUniqueOrThrow({
|
||||
where: args.where,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
|
||||
return company;
|
||||
}
|
||||
|
||||
@Mutation(() => Company, {
|
||||
nullable: true,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateCompanyAbilityHandler)
|
||||
async updateOneCompany(
|
||||
@Args() args: UpdateOneCompanyArgs,
|
||||
@PrismaSelector({ modelName: 'Company' })
|
||||
prismaSelect: PrismaSelect<'Company'>,
|
||||
): Promise<Partial<Company> | null> {
|
||||
return this.companyService.update({
|
||||
where: args.where,
|
||||
data: args.data,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.CompanyUpdateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteCompanyAbilityHandler)
|
||||
async deleteManyCompany(
|
||||
@Args() args: DeleteManyCompanyArgs,
|
||||
): Promise<AffectedRows> {
|
||||
return this.companyService.deleteMany({
|
||||
where: args.where,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Company, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateCompanyAbilityHandler)
|
||||
async createOneCompany(
|
||||
@Args() args: CreateOneCompanyArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Company' })
|
||||
prismaSelect: PrismaSelect<'Company'>,
|
||||
): Promise<Partial<Company>> {
|
||||
return this.companyService.create({
|
||||
data: {
|
||||
...args.data,
|
||||
...{ workspace: { connect: { id: workspace.id } } },
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.CompanyCreateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateCompanyAbilityHandler)
|
||||
async createManyCompany(
|
||||
@Args() args: CreateManyCompanyArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<Prisma.BatchPayload> {
|
||||
return this.companyService.createMany({
|
||||
data: args.data.map((company) => ({
|
||||
...company,
|
||||
workspaceId: workspace.id,
|
||||
})),
|
||||
skipDuplicates: args.skipDuplicates,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { CompanyService } from './company.service';
|
||||
|
||||
describe('CompanyService', () => {
|
||||
let service: CompanyService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
CompanyService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CompanyService>(CompanyService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,51 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import companiesSeed from 'src/core/company/seed-data/companies.json';
|
||||
|
||||
@Injectable()
|
||||
export class CompanyService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.company.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.company.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.company.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.company.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.company.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.company.create;
|
||||
createMany = this.prismaService.client.company.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.company.update;
|
||||
upsert = this.prismaService.client.company.upsert;
|
||||
updateMany = this.prismaService.client.company.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.company.delete;
|
||||
deleteMany = this.prismaService.client.company.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.company.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.company.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.company.groupBy;
|
||||
async createDefaultCompanies({ workspaceId }: { workspaceId: string }) {
|
||||
const companies = companiesSeed.map((company) => ({
|
||||
...company,
|
||||
workspaceId,
|
||||
}));
|
||||
await this.createMany({
|
||||
data: companies,
|
||||
});
|
||||
|
||||
return this.findMany({ where: { workspaceId } });
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Airbnb",
|
||||
"domainName": "airbnb.com",
|
||||
"address": "San Francisco",
|
||||
"employees": 5000
|
||||
},
|
||||
{
|
||||
"name": "Qonto",
|
||||
"domainName": "qonto.com",
|
||||
"address": "San Francisco",
|
||||
"employees": 800
|
||||
},
|
||||
{
|
||||
"name": "Stripe",
|
||||
"domainName": "stripe.com",
|
||||
"address": "San Francisco",
|
||||
"employees": 8000
|
||||
},
|
||||
{
|
||||
"name": "Figma",
|
||||
"domainName": "figma.com",
|
||||
"address": "San Francisco",
|
||||
"employees": 800
|
||||
},
|
||||
{
|
||||
"name": "Notion",
|
||||
"domainName": "notion.com",
|
||||
"address": "San Francisco",
|
||||
"employees": 400
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,62 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WebHookModule } from 'src/core/web-hook/web-hook.module';
|
||||
import { UserModule as UserV2Module } from 'src/coreV2/user/user.module';
|
||||
import { RefreshTokenModule as RefreshTokenV2Module } from 'src/coreV2/refresh-token/refresh-token.module';
|
||||
import { WorkspaceModule as WorkspaceV2Module } from 'src/coreV2/workspace/workspace.module';
|
||||
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
||||
import { UserModule } from 'src/core/user/user.module';
|
||||
import { RefreshTokenModule } from 'src/core/refresh-token/refresh-token.module';
|
||||
import { AuthModule } from 'src/core/auth/auth.module';
|
||||
|
||||
import { UserModule } from './user/user.module';
|
||||
import { CommentModule } from './comment/comment.module';
|
||||
import { CompanyModule } from './company/company.module';
|
||||
import { PersonModule } from './person/person.module';
|
||||
import { PipelineModule } from './pipeline/pipeline.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { WorkspaceModule } from './workspace/workspace.module';
|
||||
import { AnalyticsModule } from './analytics/analytics.module';
|
||||
import { FileModule } from './file/file.module';
|
||||
import { ClientConfigModule } from './client-config/client-config.module';
|
||||
import { AttachmentModule } from './attachment/attachment.module';
|
||||
import { ActivityModule } from './activity/activity.module';
|
||||
import { FavoriteModule } from './favorite/favorite.module';
|
||||
import { ApiKeyModule } from './api-key/api-key.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AuthModule,
|
||||
UserModule,
|
||||
CommentModule,
|
||||
CompanyModule,
|
||||
PersonModule,
|
||||
PipelineModule,
|
||||
WorkspaceModule,
|
||||
UserModule,
|
||||
RefreshTokenModule,
|
||||
AnalyticsModule,
|
||||
FileModule,
|
||||
ClientConfigModule,
|
||||
AttachmentModule,
|
||||
ActivityModule,
|
||||
FavoriteModule,
|
||||
ApiKeyModule,
|
||||
WebHookModule,
|
||||
UserV2Module,
|
||||
RefreshTokenV2Module,
|
||||
WorkspaceV2Module,
|
||||
],
|
||||
exports: [
|
||||
AuthModule,
|
||||
UserModule,
|
||||
CommentModule,
|
||||
CompanyModule,
|
||||
PersonModule,
|
||||
PipelineModule,
|
||||
WorkspaceModule,
|
||||
AnalyticsModule,
|
||||
AttachmentModule,
|
||||
FavoriteModule,
|
||||
ApiKeyModule,
|
||||
WebHookModule,
|
||||
UserV2Module,
|
||||
RefreshTokenV2Module,
|
||||
WorkspaceV2Module,
|
||||
],
|
||||
exports: [AuthModule, WorkspaceModule, UserModule, AnalyticsModule],
|
||||
})
|
||||
export class CoreModule {}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { FavoriteResolver } from './resolvers/favorite.resolver';
|
||||
import { FavoriteService } from './services/favorite.service';
|
||||
|
||||
@Module({
|
||||
imports: [AbilityModule, PrismaModule],
|
||||
providers: [FavoriteService, FavoriteResolver],
|
||||
exports: [FavoriteService],
|
||||
})
|
||||
export class FavoriteModule {}
|
||||
@ -1,175 +0,0 @@
|
||||
import {
|
||||
Resolver,
|
||||
Query,
|
||||
Args,
|
||||
Mutation,
|
||||
InputType,
|
||||
Field,
|
||||
} from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { Favorite } from 'src/core/@generated/favorite/favorite.model';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreateFavoriteAbilityHandler,
|
||||
DeleteFavoriteAbilityHandler,
|
||||
ReadFavoriteAbilityHandler,
|
||||
UpdateFavoriteAbilityHandler,
|
||||
} from 'src/ability/handlers/favorite.ability-handler';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { FavoriteService } from 'src/core/favorite/services/favorite.service';
|
||||
import { FavoriteWhereInput } from 'src/core/@generated/favorite/favorite-where.input';
|
||||
import { SortOrder } from 'src/core/@generated/prisma/sort-order.enum';
|
||||
import { UpdateOneFavoriteArgs } from 'src/core/@generated/favorite/update-one-favorite.args';
|
||||
|
||||
@InputType()
|
||||
class FavoriteMutationForPersonArgs {
|
||||
@Field(() => String)
|
||||
personId: string;
|
||||
@Field(() => Number)
|
||||
position: number;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
class FavoriteMutationForCompanyArgs {
|
||||
@Field(() => String)
|
||||
companyId: string;
|
||||
@Field(() => Number)
|
||||
position: number;
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Favorite)
|
||||
export class FavoriteResolver {
|
||||
constructor(private readonly favoriteService: FavoriteService) {}
|
||||
|
||||
@Query(() => [Favorite])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadFavoriteAbilityHandler)
|
||||
async findFavorites(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<Partial<Favorite>[]> {
|
||||
const favorites = await this.favoriteService.findMany({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
orderBy: [{ position: SortOrder.asc }],
|
||||
include: {
|
||||
person: true,
|
||||
company: {
|
||||
include: {
|
||||
accountOwner: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return favorites;
|
||||
}
|
||||
|
||||
@Mutation(() => Favorite, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateFavoriteAbilityHandler)
|
||||
async createFavoriteForPerson(
|
||||
@Args('data') args: FavoriteMutationForPersonArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Favorite' })
|
||||
prismaSelect: PrismaSelect<'Favorite'>,
|
||||
): Promise<Partial<Favorite>> {
|
||||
//To avoid duplicates we first fetch all favorites assinged by workspace
|
||||
const favorite = await this.favoriteService.findFirst({
|
||||
where: { workspaceId: workspace.id, personId: args.personId },
|
||||
});
|
||||
|
||||
if (favorite) return favorite;
|
||||
|
||||
return this.favoriteService.create({
|
||||
data: {
|
||||
person: {
|
||||
connect: { id: args.personId },
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
position: args.position,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Favorite, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateFavoriteAbilityHandler)
|
||||
async createFavoriteForCompany(
|
||||
@Args('data') args: FavoriteMutationForCompanyArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Favorite' })
|
||||
prismaSelect: PrismaSelect<'Favorite'>,
|
||||
): Promise<Partial<Favorite>> {
|
||||
//To avoid duplicates we first fetch all favorites assinged by workspace
|
||||
const favorite = await this.favoriteService.findFirst({
|
||||
where: { workspaceId: workspace.id, companyId: args.companyId },
|
||||
});
|
||||
|
||||
if (favorite) return favorite;
|
||||
|
||||
return this.favoriteService.create({
|
||||
data: {
|
||||
company: {
|
||||
connect: { id: args.companyId },
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
position: args.position,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Favorite, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateFavoriteAbilityHandler)
|
||||
async updateOneFavorites(
|
||||
@Args() args: UpdateOneFavoriteArgs,
|
||||
@PrismaSelector({ modelName: 'Favorite' })
|
||||
prismaSelect: PrismaSelect<'Favorite'>,
|
||||
): Promise<Partial<Favorite>> {
|
||||
return this.favoriteService.update({
|
||||
data: args.data,
|
||||
where: args.where,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.FavoriteUpdateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => Favorite, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteFavoriteAbilityHandler)
|
||||
async deleteFavorite(
|
||||
@Args('where') args: FavoriteWhereInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Favorite' })
|
||||
prismaSelect: PrismaSelect<'Favorite'>,
|
||||
): Promise<Partial<Favorite>> {
|
||||
const favorite = await this.favoriteService.findFirst({
|
||||
where: { ...args, workspaceId: workspace.id },
|
||||
});
|
||||
|
||||
return this.favoriteService.delete({
|
||||
where: { id: favorite?.id },
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class FavoriteService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.favorite.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.favorite.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.favorite.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.favorite.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.favorite.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.favorite.create;
|
||||
createMany = this.prismaService.client.favorite.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.favorite.update;
|
||||
upsert = this.prismaService.client.favorite.upsert;
|
||||
updateMany = this.prismaService.client.favorite.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.favorite.delete;
|
||||
deleteMany = this.prismaService.client.favorite.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.favorite.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.favorite.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.favorite.groupBy;
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { CommentService } from 'src/core/comment/comment.service';
|
||||
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||
|
||||
import { PersonRelationsResolver } from './person-relations.resolver';
|
||||
import { PersonService } from './person.service';
|
||||
|
||||
describe('PersonRelationsResolver', () => {
|
||||
let resolver: PersonRelationsResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PersonRelationsResolver,
|
||||
{
|
||||
provide: PersonService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: ActivityService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: CommentService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<PersonRelationsResolver>(PersonRelationsResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,76 +0,0 @@
|
||||
import { Resolver, Root, ResolveField, Int } from '@nestjs/graphql';
|
||||
|
||||
import { Comment } from 'src/core/@generated/comment/comment.model';
|
||||
import { Person } from 'src/core/@generated/person/person.model';
|
||||
import { CommentService } from 'src/core/comment/comment.service';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { Activity } from 'src/core/@generated/activity/activity.model';
|
||||
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||
|
||||
@Resolver(() => Person)
|
||||
export class PersonRelationsResolver {
|
||||
constructor(
|
||||
private readonly activityService: ActivityService,
|
||||
private readonly commentService: CommentService,
|
||||
) {}
|
||||
|
||||
@ResolveField(() => [Activity], {
|
||||
nullable: false,
|
||||
})
|
||||
async activities(
|
||||
@Root() person: Person,
|
||||
@PrismaSelector({ modelName: 'Activity' })
|
||||
prismaSelect: PrismaSelect<'Activity'>,
|
||||
): Promise<Partial<Activity>[]> {
|
||||
return await this.activityService.findMany({
|
||||
where: {
|
||||
activityTargets: {
|
||||
some: {
|
||||
personId: person.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => [Comment], {
|
||||
nullable: false,
|
||||
})
|
||||
async comments(
|
||||
@Root() person: Person,
|
||||
@PrismaSelector({ modelName: 'Comment' })
|
||||
prismaSelect: PrismaSelect<'Comment'>,
|
||||
): Promise<Partial<Comment>[]> {
|
||||
return this.commentService.findMany({
|
||||
where: {
|
||||
activity: {
|
||||
activityTargets: {
|
||||
some: {
|
||||
personId: person.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => Int, {
|
||||
nullable: false,
|
||||
})
|
||||
async _activityCount(@Root() person: Person): Promise<number> {
|
||||
return this.activityService.count({
|
||||
where: {
|
||||
activityTargets: {
|
||||
some: {
|
||||
personId: person.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CommentModule } from 'src/core/comment/comment.module';
|
||||
import { ActivityModule } from 'src/core/activity/activity.module';
|
||||
import { FileModule } from 'src/core/file/file.module';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { PersonService } from './person.service';
|
||||
import { PersonResolver } from './person.resolver';
|
||||
import { PersonRelationsResolver } from './person-relations.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CommentModule,
|
||||
ActivityModule,
|
||||
FileModule,
|
||||
AbilityModule,
|
||||
PrismaModule,
|
||||
],
|
||||
providers: [PersonService, PersonResolver, PersonRelationsResolver],
|
||||
exports: [PersonService],
|
||||
})
|
||||
export class PersonModule {}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
|
||||
import { PersonService } from './person.service';
|
||||
import { PersonResolver } from './person.resolver';
|
||||
|
||||
describe('PersonResolver', () => {
|
||||
let resolver: PersonResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PersonResolver,
|
||||
{
|
||||
provide: PersonService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: FileUploadService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<PersonResolver>(PersonResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,214 +0,0 @@
|
||||
import {
|
||||
Resolver,
|
||||
Query,
|
||||
Args,
|
||||
Mutation,
|
||||
ResolveField,
|
||||
Parent,
|
||||
} from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { Person } from 'src/core/@generated/person/person.model';
|
||||
import { FindManyPersonArgs } from 'src/core/@generated/person/find-many-person.args';
|
||||
import { UpdateOnePersonArgs } from 'src/core/@generated/person/update-one-person.args';
|
||||
import { CreateOnePersonArgs } from 'src/core/@generated/person/create-one-person.args';
|
||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||
import { DeleteManyPersonArgs } from 'src/core/@generated/person/delete-many-person.args';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreatePersonAbilityHandler,
|
||||
DeletePersonAbilityHandler,
|
||||
ReadPersonAbilityHandler,
|
||||
UpdatePersonAbilityHandler,
|
||||
} from 'src/ability/handlers/person.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { CreateManyPersonArgs } from 'src/core/@generated/person/create-many-person.args';
|
||||
|
||||
import { PersonService } from './person.service';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Person)
|
||||
export class PersonResolver {
|
||||
constructor(
|
||||
private readonly personService: PersonService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
) {}
|
||||
|
||||
@Query(() => [Person], {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadPersonAbilityHandler)
|
||||
async findManyPerson(
|
||||
@Args() args: FindManyPersonArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'Person' })
|
||||
prismaSelect: PrismaSelect<'Person'>,
|
||||
): Promise<Partial<Person>[]> {
|
||||
return this.personService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).Person],
|
||||
}
|
||||
: accessibleBy(ability).Person,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => Person)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadPersonAbilityHandler)
|
||||
async findUniquePerson(
|
||||
@Args('id') id: string,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'Person' })
|
||||
prismaSelect: PrismaSelect<'Person'>,
|
||||
): Promise<Partial<Person>> {
|
||||
return this.personService.findUniqueOrThrow({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => String, {
|
||||
nullable: false,
|
||||
})
|
||||
displayName(@Parent() parent: Person): string {
|
||||
return `${parent.firstName ?? ''} ${parent.lastName ?? ''}`;
|
||||
}
|
||||
|
||||
@Mutation(() => Person, {
|
||||
nullable: true,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdatePersonAbilityHandler)
|
||||
async updateOnePerson(
|
||||
@Args() args: UpdateOnePersonArgs,
|
||||
@PrismaSelector({ modelName: 'Person' })
|
||||
prismaSelect: PrismaSelect<'Person'>,
|
||||
): Promise<Partial<Person> | null> {
|
||||
// TODO: Do a proper check with recursion testing on args in a more generic place
|
||||
for (const key in args.data) {
|
||||
if (args.data[key]) {
|
||||
for (const subKey in args.data[key]) {
|
||||
if (JSON.stringify(args.data[key][subKey]) === '{}') {
|
||||
delete args.data[key][subKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(args.data[key]) === '{}') {
|
||||
delete args.data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return this.personService.update({
|
||||
where: args.where,
|
||||
data: args.data,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.PersonUpdateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeletePersonAbilityHandler)
|
||||
async deleteManyPerson(
|
||||
@Args() args: DeleteManyPersonArgs,
|
||||
): Promise<AffectedRows> {
|
||||
return this.personService.deleteMany({
|
||||
where: args.where,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Person, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreatePersonAbilityHandler)
|
||||
async createOnePerson(
|
||||
@Args() args: CreateOnePersonArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Person' })
|
||||
prismaSelect: PrismaSelect<'Person'>,
|
||||
): Promise<Partial<Person>> {
|
||||
return this.personService.create({
|
||||
data: {
|
||||
...args.data,
|
||||
...{ workspace: { connect: { id: workspace.id } } },
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.PersonCreateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreatePersonAbilityHandler)
|
||||
async createManyPerson(
|
||||
@Args() args: CreateManyPersonArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<Prisma.BatchPayload> {
|
||||
return this.personService.createMany({
|
||||
data: args.data.map((person) => ({
|
||||
...person,
|
||||
workspaceId: workspace.id,
|
||||
})),
|
||||
skipDuplicates: args.skipDuplicates,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => String)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdatePersonAbilityHandler)
|
||||
async uploadPersonPicture(
|
||||
@Args('id') id: string,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
): Promise<string> {
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder: FileFolder.PersonPicture,
|
||||
});
|
||||
|
||||
await this.personService.update({
|
||||
where: { id },
|
||||
data: {
|
||||
avatarUrl: paths[0],
|
||||
},
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { PersonService } from './person.service';
|
||||
|
||||
describe('PersonService', () => {
|
||||
let service: PersonService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PersonService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PersonService>(PersonService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,58 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Company } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import peopleSeed from 'src/core/person/seed-data/people.json';
|
||||
|
||||
@Injectable()
|
||||
export class PersonService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.person.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.person.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.person.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.person.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.person.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.person.create;
|
||||
createMany = this.prismaService.client.person.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.person.update;
|
||||
upsert = this.prismaService.client.person.upsert;
|
||||
updateMany = this.prismaService.client.person.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.person.delete;
|
||||
deleteMany = this.prismaService.client.person.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.person.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.person.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.person.groupBy;
|
||||
async createDefaultPeople({
|
||||
workspaceId,
|
||||
companies,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
companies: Company[];
|
||||
}) {
|
||||
const people = peopleSeed.map((person, i) => ({
|
||||
...person,
|
||||
companyId: companies[i].id || null,
|
||||
workspaceId,
|
||||
}));
|
||||
return this.createMany({
|
||||
data: people,
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
import { PipelineService } from './services/pipeline.service';
|
||||
import { PipelineResolver } from './resolvers/pipeline.resolver';
|
||||
import { PipelineStageResolver } from './resolvers/pipeline-stage.resolver';
|
||||
import { PipelineProgressResolver } from './resolvers/pipeline-progress.resolver';
|
||||
import { PipelineStageService } from './services/pipeline-stage.service';
|
||||
import { PipelineProgressService } from './services/pipeline-progress.service';
|
||||
|
||||
@Module({
|
||||
imports: [AbilityModule, PrismaModule],
|
||||
providers: [
|
||||
PipelineService,
|
||||
PipelineStageService,
|
||||
PipelineProgressService,
|
||||
PipelineResolver,
|
||||
PipelineStageResolver,
|
||||
PipelineProgressResolver,
|
||||
],
|
||||
exports: [PipelineService, PipelineStageService, PipelineProgressService],
|
||||
})
|
||||
export class PipelineModule {}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { PipelineProgressResolver } from './pipeline-progress.resolver';
|
||||
|
||||
describe('PipelineProgressResolver', () => {
|
||||
let resolver: PipelineProgressResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PipelineProgressResolver,
|
||||
{
|
||||
provide: PipelineProgressService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<PipelineProgressResolver>(PipelineProgressResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,126 +0,0 @@
|
||||
import { Resolver, Args, Query, Mutation } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { FindManyPipelineProgressArgs } from 'src/core/@generated/pipeline-progress/find-many-pipeline-progress.args';
|
||||
import { PipelineProgress } from 'src/core/@generated/pipeline-progress/pipeline-progress.model';
|
||||
import { UpdateOnePipelineProgressArgs } from 'src/core/@generated/pipeline-progress/update-one-pipeline-progress.args';
|
||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||
import { DeleteManyPipelineProgressArgs } from 'src/core/@generated/pipeline-progress/delete-many-pipeline-progress.args';
|
||||
import { CreateOnePipelineProgressArgs } from 'src/core/@generated/pipeline-progress/create-one-pipeline-progress.args';
|
||||
import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreatePipelineProgressAbilityHandler,
|
||||
ReadPipelineProgressAbilityHandler,
|
||||
UpdatePipelineProgressAbilityHandler,
|
||||
DeletePipelineProgressAbilityHandler,
|
||||
} from 'src/ability/handlers/pipeline-progress.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import {
|
||||
PrismaSelector,
|
||||
PrismaSelect,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => PipelineProgress)
|
||||
export class PipelineProgressResolver {
|
||||
constructor(
|
||||
private readonly pipelineProgressService: PipelineProgressService,
|
||||
) {}
|
||||
|
||||
@Query(() => [PipelineProgress])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadPipelineProgressAbilityHandler)
|
||||
async findManyPipelineProgress(
|
||||
@Args() args: FindManyPipelineProgressArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'PipelineProgress' })
|
||||
prismaSelect: PrismaSelect<'PipelineProgress'>,
|
||||
): Promise<Partial<PipelineProgress>[]> {
|
||||
return this.pipelineProgressService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).PipelineProgress],
|
||||
}
|
||||
: accessibleBy(ability).PipelineProgress,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PipelineProgress, {
|
||||
nullable: true,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdatePipelineProgressAbilityHandler)
|
||||
async updateOnePipelineProgress(
|
||||
@Args() args: UpdateOnePipelineProgressArgs,
|
||||
@PrismaSelector({ modelName: 'PipelineProgress' })
|
||||
prismaSelect: PrismaSelect<'PipelineProgress'>,
|
||||
): Promise<Partial<PipelineProgress> | null> {
|
||||
// TODO: Do a proper check with recursion testing on args in a more generic place
|
||||
for (const key in args.data) {
|
||||
if (args.data[key]) {
|
||||
for (const subKey in args.data[key]) {
|
||||
if (JSON.stringify(args.data[key][subKey]) === '{}') {
|
||||
delete args.data[key][subKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(args.data[key]) === '{}') {
|
||||
delete args.data[key];
|
||||
}
|
||||
}
|
||||
return this.pipelineProgressService.update({
|
||||
where: args.where,
|
||||
data: args.data,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.PipelineProgressUpdateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => AffectedRows, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeletePipelineProgressAbilityHandler)
|
||||
async deleteManyPipelineProgress(
|
||||
@Args() args: DeleteManyPipelineProgressArgs,
|
||||
): Promise<AffectedRows> {
|
||||
return this.pipelineProgressService.deleteMany({
|
||||
where: args.where,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PipelineProgress, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreatePipelineProgressAbilityHandler)
|
||||
async createOnePipelineProgress(
|
||||
@Args() args: CreateOnePipelineProgressArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'PipelineProgress' })
|
||||
prismaSelect: PrismaSelect<'PipelineProgress'>,
|
||||
): Promise<Partial<PipelineProgress>> {
|
||||
return this.pipelineProgressService.create({
|
||||
data: {
|
||||
...args.data,
|
||||
...{ workspace: { connect: { id: workspace.id } } },
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.PipelineProgressCreateArgs);
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { PipelineStageResolver } from './pipeline-stage.resolver';
|
||||
|
||||
describe('PipelineStageResolver', () => {
|
||||
let resolver: PipelineStageResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PipelineStageResolver,
|
||||
{
|
||||
provide: PipelineStageService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<PipelineStageResolver>(PipelineStageResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,149 +0,0 @@
|
||||
import { Resolver, Args, Query, Mutation } from '@nestjs/graphql';
|
||||
import {
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { PipelineStage } from 'src/core/@generated/pipeline-stage/pipeline-stage.model';
|
||||
import { FindManyPipelineStageArgs } from 'src/core/@generated/pipeline-stage/find-many-pipeline-stage.args';
|
||||
import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreatePipelineStageAbilityHandler,
|
||||
DeletePipelineStageAbilityHandler,
|
||||
ReadPipelineStageAbilityHandler,
|
||||
UpdatePipelineStageAbilityHandler,
|
||||
} from 'src/ability/handlers/pipeline-stage.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import {
|
||||
PrismaSelector,
|
||||
PrismaSelect,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { UpdateOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/update-one-pipeline-stage.args';
|
||||
import { CreateOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/create-one-pipeline-stage.args';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { DeleteOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/delete-one-pipeline-stage.args';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => PipelineStage)
|
||||
export class PipelineStageResolver {
|
||||
constructor(private readonly pipelineStageService: PipelineStageService) {}
|
||||
|
||||
@Mutation(() => PipelineStage, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreatePipelineStageAbilityHandler)
|
||||
async createOnePipelineStage(
|
||||
@Args() args: CreateOnePipelineStageArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'PipelineStage' })
|
||||
prismaSelect: PrismaSelect<'PipelineStage'>,
|
||||
): Promise<Partial<PipelineStage>> {
|
||||
return this.pipelineStageService.create({
|
||||
data: {
|
||||
...args.data,
|
||||
workspace: { connect: { id: workspace.id } },
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.PipelineStageCreateArgs);
|
||||
}
|
||||
|
||||
@Query(() => [PipelineStage])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadPipelineStageAbilityHandler)
|
||||
async findManyPipelineStage(
|
||||
@Args() args: FindManyPipelineStageArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'PipelineStage' })
|
||||
prismaSelect: PrismaSelect<'PipelineStage'>,
|
||||
): Promise<Partial<PipelineStage>[]> {
|
||||
return this.pipelineStageService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).PipelineStage],
|
||||
}
|
||||
: accessibleBy(ability).PipelineStage,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PipelineStage, {
|
||||
nullable: true,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdatePipelineStageAbilityHandler)
|
||||
async updateOnePipelineStage(
|
||||
@Args() args: UpdateOnePipelineStageArgs,
|
||||
@PrismaSelector({ modelName: 'PipelineProgress' })
|
||||
prismaSelect: PrismaSelect<'PipelineProgress'>,
|
||||
): Promise<Partial<PipelineStage> | null> {
|
||||
return this.pipelineStageService.update({
|
||||
where: args.where,
|
||||
data: args.data,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.PipelineProgressUpdateArgs);
|
||||
}
|
||||
|
||||
@Mutation(() => PipelineStage, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeletePipelineStageAbilityHandler)
|
||||
async deleteOnePipelineStage(
|
||||
@Args() args: DeleteOnePipelineStageArgs,
|
||||
): Promise<PipelineStage> {
|
||||
const pipelineStageToDelete = await this.pipelineStageService.findUnique({
|
||||
where: args.where,
|
||||
});
|
||||
|
||||
if (!pipelineStageToDelete) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
const { pipelineId } = pipelineStageToDelete;
|
||||
|
||||
const remainingPipelineStages = await this.pipelineStageService.findMany({
|
||||
orderBy: { position: 'asc' },
|
||||
where: {
|
||||
pipelineId,
|
||||
NOT: { id: pipelineStageToDelete.id },
|
||||
},
|
||||
});
|
||||
|
||||
if (!remainingPipelineStages.length) {
|
||||
throw new ForbiddenException(
|
||||
`Deleting last pipeline stage is not allowed`,
|
||||
);
|
||||
}
|
||||
|
||||
const deletedPipelineStage = await this.pipelineStageService.delete({
|
||||
where: args.where,
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
remainingPipelineStages.map((pipelineStage, index) => {
|
||||
if (pipelineStage.position === index) return;
|
||||
|
||||
return this.pipelineStageService.update({
|
||||
data: { position: index },
|
||||
where: { id: pipelineStage.id },
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return deletedPipelineStage;
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { PipelineResolver } from './pipeline.resolver';
|
||||
|
||||
describe('PipelineResolver', () => {
|
||||
let resolver: PipelineResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PipelineResolver,
|
||||
{
|
||||
provide: PipelineService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<PipelineResolver>(PipelineResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,44 +0,0 @@
|
||||
import { Resolver, Args, Query } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { Pipeline } from 'src/core/@generated/pipeline/pipeline.model';
|
||||
import { FindManyPipelineArgs } from 'src/core/@generated/pipeline/find-many-pipeline.args';
|
||||
import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import { ReadPipelineAbilityHandler } from 'src/ability/handlers/pipeline.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import {
|
||||
PrismaSelector,
|
||||
PrismaSelect,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Pipeline)
|
||||
export class PipelineResolver {
|
||||
constructor(private readonly pipelineService: PipelineService) {}
|
||||
|
||||
@Query(() => [Pipeline])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadPipelineAbilityHandler)
|
||||
async findManyPipeline(
|
||||
@Args() args: FindManyPipelineArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'Pipeline' })
|
||||
prismaSelect: PrismaSelect<'Pipeline'>,
|
||||
): Promise<Partial<Pipeline>[]> {
|
||||
return this.pipelineService.findMany({
|
||||
...args,
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).Pipeline],
|
||||
}
|
||||
: accessibleBy(ability).Pipeline,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "New",
|
||||
"color": "red",
|
||||
"position": 0,
|
||||
"type": "open"
|
||||
},
|
||||
{
|
||||
"name": "Screening",
|
||||
"color": "purple",
|
||||
"position": 1,
|
||||
"type": "ongoing"
|
||||
},
|
||||
{
|
||||
"name": "Meeting",
|
||||
"color": "sky",
|
||||
"position": 2,
|
||||
"type": "ongoing"
|
||||
},
|
||||
{
|
||||
"name": "Proposal",
|
||||
"color": "turquoise",
|
||||
"position": 3,
|
||||
"type": "ongoing"
|
||||
},
|
||||
{
|
||||
"name": "Customer",
|
||||
"color": "yellow",
|
||||
"position": 4,
|
||||
"type": "won"
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "Sales pipeline",
|
||||
"icon": "💰",
|
||||
"pipelineProgressableType": "Company"
|
||||
}
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { PipelineProgressService } from './pipeline-progress.service';
|
||||
|
||||
describe('PipelineProgressService', () => {
|
||||
let service: PipelineProgressService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PipelineProgressService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PipelineProgressService>(PipelineProgressService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,41 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class PipelineProgressService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.pipelineProgress.findFirst;
|
||||
findFirstOrThrow =
|
||||
this.prismaService.client.pipelineProgress.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.pipelineProgress.findUnique;
|
||||
findUniqueOrThrow =
|
||||
this.prismaService.client.pipelineProgress.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.pipelineProgress.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.pipelineProgress.create;
|
||||
createMany = this.prismaService.client.pipelineProgress.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.pipelineProgress.update;
|
||||
upsert = this.prismaService.client.pipelineProgress.upsert;
|
||||
updateMany = this.prismaService.client.pipelineProgress.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.pipelineProgress.delete;
|
||||
deleteMany = this.prismaService.client.pipelineProgress.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.pipelineProgress.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.pipelineProgress.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.pipelineProgress.groupBy;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { PipelineStageService } from './pipeline-stage.service';
|
||||
|
||||
describe('PipelineStageService', () => {
|
||||
let service: PipelineStageService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PipelineStageService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PipelineStageService>(PipelineStageService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,58 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import seedPipelineStages from 'src/core/pipeline/seed-data/pipeline-stages.json';
|
||||
|
||||
@Injectable()
|
||||
export class PipelineStageService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.pipelineStage.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.pipelineStage.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.pipelineStage.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.pipelineStage.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.pipelineStage.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.pipelineStage.create;
|
||||
createMany = this.prismaService.client.pipelineStage.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.pipelineStage.update;
|
||||
upsert = this.prismaService.client.pipelineStage.upsert;
|
||||
updateMany = this.prismaService.client.pipelineStage.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.pipelineStage.delete;
|
||||
deleteMany = this.prismaService.client.pipelineStage.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.pipelineStage.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.pipelineStage.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.pipelineStage.groupBy;
|
||||
|
||||
// Customs
|
||||
async createDefaultPipelineStages({
|
||||
workspaceId,
|
||||
pipelineId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
pipelineId: string;
|
||||
}) {
|
||||
const pipelineStages = seedPipelineStages.map((pipelineStage) => ({
|
||||
...pipelineStage,
|
||||
workspaceId,
|
||||
pipelineId,
|
||||
}));
|
||||
return this.createMany({
|
||||
data: pipelineStages,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { PipelineService } from './pipeline.service';
|
||||
|
||||
describe('PipelineService', () => {
|
||||
let service: PipelineService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PipelineService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PipelineService>(PipelineService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,56 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PipelineProgressableType } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import seedSalesPipeline from 'src/core/pipeline/seed-data/sales-pipeline.json';
|
||||
|
||||
@Injectable()
|
||||
export class PipelineService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.pipeline.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.pipeline.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.pipeline.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.pipeline.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.pipeline.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.pipeline.create;
|
||||
createMany = this.prismaService.client.pipeline.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.pipeline.update;
|
||||
upsert = this.prismaService.client.pipeline.upsert;
|
||||
updateMany = this.prismaService.client.pipeline.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.pipeline.delete;
|
||||
deleteMany = this.prismaService.client.pipeline.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.pipeline.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.pipeline.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.pipeline.groupBy;
|
||||
|
||||
// Customs
|
||||
async createDefaultPipeline({ workspaceId }: { workspaceId: string }) {
|
||||
const pipeline = {
|
||||
...seedSalesPipeline,
|
||||
pipelineProgressableType:
|
||||
seedSalesPipeline.pipelineProgressableType as PipelineProgressableType,
|
||||
workspaceId,
|
||||
};
|
||||
|
||||
return this.create({
|
||||
data: pipeline,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsDate, IsNotEmpty } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class CreateRefreshTokenInput {
|
||||
@IsDate()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
expiresAt: Date;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import {
|
||||
BeforeCreateOneHook,
|
||||
CreateOneInputType,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
|
||||
export class BeforeCreateOneRefreshToken<T extends RefreshToken>
|
||||
implements BeforeCreateOneHook<T, any>
|
||||
{
|
||||
async run(
|
||||
instance: CreateOneInputType<T>,
|
||||
context: any,
|
||||
): Promise<CreateOneInputType<T>> {
|
||||
const userId = context?.req?.user?.user?.id;
|
||||
|
||||
instance.input.userId = userId;
|
||||
// FIXME: These fields should be autogenerated, we need to run a migration for this
|
||||
instance.input.id = uuidv4();
|
||||
instance.input.updatedAt = new Date();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
|
||||
import { RefreshToken } from './refresh-token.entity';
|
||||
|
||||
import { CreateRefreshTokenInput } from './dtos/create-refresh-token.input';
|
||||
|
||||
export const refreshTokenAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: RefreshToken,
|
||||
DTOClass: RefreshToken,
|
||||
CreateDTOClass: CreateRefreshTokenInput,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
50
server/src/core/refresh-token/refresh-token.entity.ts
Normal file
50
server/src/core/refresh-token/refresh-token.entity.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BeforeCreateOne, IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
|
||||
import { BeforeCreateOneRefreshToken } from './hooks/before-create-one-refresh-token.hook';
|
||||
|
||||
@Entity({ name: 'refreshToken', schema: 'core' })
|
||||
@ObjectType('RefreshToken')
|
||||
@BeforeCreateOne(BeforeCreateOneRefreshToken)
|
||||
export class RefreshToken {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.refreshTokens)
|
||||
@JoinColumn({ name: 'userId' })
|
||||
user: User;
|
||||
|
||||
@Column()
|
||||
userId: string;
|
||||
|
||||
@Field()
|
||||
@Column('timestamp with time zone')
|
||||
expiresAt: Date;
|
||||
|
||||
@Column('timestamp with time zone', { nullable: true })
|
||||
deletedAt: Date | null;
|
||||
|
||||
@Column('timestamp with time zone', { nullable: true })
|
||||
revokedAt: Date | null;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
25
server/src/core/refresh-token/refresh-token.module.ts
Normal file
25
server/src/core/refresh-token/refresh-token.module.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import config from '../../../ormconfig';
|
||||
|
||||
import { RefreshToken } from './refresh-token.entity';
|
||||
import { refreshTokenAutoResolverOpts } from './refresh-token.auto-resolver-opts';
|
||||
|
||||
import { RefreshTokenService } from './services/refresh-token.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRoot(config),
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [NestjsQueryTypeOrmModule.forFeature([RefreshToken])],
|
||||
services: [RefreshTokenService],
|
||||
resolvers: refreshTokenAutoResolverOpts,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class RefreshTokenModule {}
|
||||
@ -0,0 +1,28 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
|
||||
import { RefreshTokenService } from './refresh-token.service';
|
||||
|
||||
describe('RefreshTokenService', () => {
|
||||
let service: RefreshTokenService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
RefreshTokenService,
|
||||
{
|
||||
provide: getRepositoryToken(RefreshToken),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<RefreshTokenService>(RefreshTokenService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
|
||||
export class RefreshTokenService extends TypeOrmQueryService<RefreshToken> {}
|
||||
33
server/src/core/user/dtos/workspace-member.dto.ts
Normal file
33
server/src/core/user/dtos/workspace-member.dto.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
@ObjectType('UserWorkspaceMemberName')
|
||||
export class UserWorkspaceMemberName {
|
||||
@Field({ nullable: false })
|
||||
firstName: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
@ObjectType('UserWorkspaceMember')
|
||||
export class UserWorkspaceMember {
|
||||
@IDField(() => ID)
|
||||
id: string;
|
||||
|
||||
@Field(() => UserWorkspaceMemberName)
|
||||
name: UserWorkspaceMemberName;
|
||||
|
||||
@Field({ nullable: false })
|
||||
colorScheme: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
avatarUrl: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
locale: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
allowImpersonation: boolean;
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
|
||||
@ -14,11 +13,7 @@ describe('UserService', () => {
|
||||
providers: [
|
||||
UserService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
{
|
||||
provide: WorkspaceService,
|
||||
provide: getRepositoryToken(User),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
85
server/src/core/user/services/user.service.ts
Normal file
85
server/src/core/user/services/user.service.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { UserWorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
|
||||
export class UserService extends TypeOrmQueryService<User> {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
) {
|
||||
super(userRepository);
|
||||
}
|
||||
|
||||
async loadWorkspaceMember(user: User) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
user.defaultWorkspace.id,
|
||||
);
|
||||
|
||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
||||
dataSourceMetadata,
|
||||
);
|
||||
|
||||
const workspaceMembers = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${user.id}'`,
|
||||
);
|
||||
|
||||
assert(workspaceMembers.length === 1, 'WorkspaceMember not found');
|
||||
|
||||
const userWorkspaceMember = new UserWorkspaceMember();
|
||||
|
||||
userWorkspaceMember.id = workspaceMembers[0].id;
|
||||
userWorkspaceMember.colorScheme = workspaceMembers[0].colorScheme;
|
||||
userWorkspaceMember.locale = workspaceMembers[0].locale;
|
||||
userWorkspaceMember.allowImpersonation =
|
||||
workspaceMembers[0].allowImpersonation;
|
||||
userWorkspaceMember.avatarUrl = workspaceMembers[0].avatarUrl;
|
||||
userWorkspaceMember.name = {
|
||||
firstName: workspaceMembers[0].nameFirstName,
|
||||
lastName: workspaceMembers[0].nameLastName,
|
||||
};
|
||||
|
||||
return userWorkspaceMember;
|
||||
}
|
||||
|
||||
async createWorkspaceMember(user: User, avatarUrl?: string) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
user.defaultWorkspace.id,
|
||||
);
|
||||
|
||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
||||
dataSourceMetadata,
|
||||
);
|
||||
|
||||
await workspaceDataSource?.query(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."workspaceMember"
|
||||
("nameFirstName", "nameLastName", "colorScheme", "userId", "allowImpersonation", "avatarUrl")
|
||||
VALUES ('${user.firstName}', '${user.lastName}', 'Light', '${
|
||||
user.id
|
||||
}', true, '${avatarUrl ?? ''}')`,
|
||||
);
|
||||
}
|
||||
|
||||
async deleteUser({
|
||||
workspaceId: _workspaceId,
|
||||
userId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const user = await this.userRepository.findBy({ id: userId });
|
||||
assert(user, 'User not found');
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
38
server/src/core/user/user.auto-resolver-opts.ts
Normal file
38
server/src/core/user/user.auto-resolver-opts.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
ReadResolverOpts,
|
||||
PagingStrategies,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
|
||||
export const userAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: User,
|
||||
DTOClass: User,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
74
server/src/core/user/user.entity.ts
Normal file
74
server/src/core/user/user.entity.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { ID, Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
} from 'typeorm';
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { UserWorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
||||
|
||||
@Entity({ name: 'user', schema: 'core' })
|
||||
@ObjectType('User')
|
||||
export class User {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: true })
|
||||
firstName: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: true })
|
||||
lastName: string;
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
email: string;
|
||||
|
||||
@Field()
|
||||
@Column({ default: false })
|
||||
emailVerified: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ default: false })
|
||||
disabled: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
passwordHash: string;
|
||||
|
||||
@Field()
|
||||
@Column({ default: false })
|
||||
canImpersonate: boolean;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
deletedAt: Date;
|
||||
|
||||
@Field(() => Workspace, { nullable: false })
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.users)
|
||||
defaultWorkspace: Workspace;
|
||||
|
||||
@OneToMany(() => RefreshToken, (refreshToken) => refreshToken.user)
|
||||
refreshTokens: RefreshToken[];
|
||||
|
||||
@Field(() => UserWorkspaceMember, { nullable: false })
|
||||
workspaceMember: UserWorkspaceMember;
|
||||
}
|
||||
@ -1,23 +1,34 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FileModule } from 'src/core/file/file.module';
|
||||
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
||||
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { UserResolver } from 'src/core/user/user.resolver';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
import { UserResolver } from './user.resolver';
|
||||
import config from '../../../ormconfig';
|
||||
|
||||
import { userAutoResolverOpts } from './user.auto-resolver-opts';
|
||||
|
||||
import { UserService } from './services/user.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRoot(config),
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [NestjsQueryTypeOrmModule.forFeature([User]), TypeORMModule],
|
||||
resolvers: userAutoResolverOpts,
|
||||
}),
|
||||
DataSourceModule,
|
||||
FileModule,
|
||||
WorkspaceModule,
|
||||
EnvironmentModule,
|
||||
AbilityModule,
|
||||
PrismaModule,
|
||||
],
|
||||
providers: [UserService, UserResolver],
|
||||
exports: [UserService],
|
||||
providers: [UserService, UserResolver, TypeORMService],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
|
||||
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: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: FileUploadService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<UserResolver>(UserResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,48 +1,32 @@
|
||||
import {
|
||||
Args,
|
||||
Resolver,
|
||||
Query,
|
||||
ResolveField,
|
||||
Args,
|
||||
Parent,
|
||||
ResolveField,
|
||||
Mutation,
|
||||
} from '@nestjs/graphql';
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
import { SupportDriver } from 'src/integrations/environment/interfaces/support.interface';
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { FindManyUserArgs } from 'src/core/@generated/user/find-many-user.args';
|
||||
import { User } from 'src/core/@generated/user/user.model';
|
||||
import { ExceptionFilter } from 'src/filters/exception.filter';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
DeleteUserAbilityHandler,
|
||||
ReadUserAbilityHandler,
|
||||
UpdateUserAbilityHandler,
|
||||
} from 'src/ability/handlers/user.ability-handler';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { UpdateOneUserArgs } from 'src/core/@generated/user/update-one-user.args';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { UserWorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
import { UserService } from './services/user.service';
|
||||
|
||||
const getHMACKey = (email?: string, key?: string | null) => {
|
||||
if (!email || !key) return null;
|
||||
@ -56,85 +40,24 @@ const getHMACKey = (email?: string, key?: string | null) => {
|
||||
export class UserResolver {
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
private environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
@Query(() => User)
|
||||
async currentUser(
|
||||
@AuthUser() { id }: User,
|
||||
@PrismaSelector({ modelName: 'User' })
|
||||
prismaSelect: PrismaSelect<'User'>,
|
||||
) {
|
||||
const select = prismaSelect.value;
|
||||
|
||||
const user = await this.userService.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select,
|
||||
async currentUser(@AuthUser() { id }: User) {
|
||||
const user = await this.userService.findById(id, {
|
||||
relations: [{ name: 'defaultWorkspace', query: {} }],
|
||||
});
|
||||
assert(user, 'User not found');
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@UseFilters(ExceptionFilter)
|
||||
@Query(() => [User], {
|
||||
@ResolveField(() => UserWorkspaceMember, {
|
||||
nullable: false,
|
||||
})
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadUserAbilityHandler)
|
||||
async findManyUser(
|
||||
@Args() args: FindManyUserArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'User' })
|
||||
prismaSelect: PrismaSelect<'User'>,
|
||||
): Promise<Partial<User>[]> {
|
||||
return await this.userService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).User],
|
||||
}
|
||||
: accessibleBy(ability).User,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => User)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateUserAbilityHandler)
|
||||
async updateUser(
|
||||
@Args() args: UpdateOneUserArgs,
|
||||
@AuthUser() { id }: User,
|
||||
@PrismaSelector({ modelName: 'User' })
|
||||
prismaSelect: PrismaSelect<'User'>,
|
||||
) {
|
||||
const user = await this.userService.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
assert(user, 'User not found');
|
||||
|
||||
return this.userService.update({
|
||||
where: args.where,
|
||||
data: args.data,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.UserUpdateArgs);
|
||||
}
|
||||
|
||||
@ResolveField(() => String, {
|
||||
nullable: false,
|
||||
})
|
||||
displayName(@Parent() parent: User): string {
|
||||
return `${parent.firstName ?? ''} ${parent.lastName ?? ''}`;
|
||||
async workspaceMember(@Parent() user: User): Promise<UserWorkspaceMember> {
|
||||
return this.userService.loadWorkspaceMember(user);
|
||||
}
|
||||
|
||||
@ResolveField(() => String, {
|
||||
@ -154,6 +77,10 @@ export class UserResolver {
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
): Promise<string> {
|
||||
if (!id) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
const fileFolder = FileFolder.ProfilePicture;
|
||||
@ -165,20 +92,11 @@ export class UserResolver {
|
||||
fileFolder,
|
||||
});
|
||||
|
||||
await this.userService.update({
|
||||
where: { id },
|
||||
data: {
|
||||
avatarUrl: paths[0],
|
||||
},
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
@Mutation(() => User)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteUserAbilityHandler)
|
||||
async deleteUserAccount(
|
||||
async deleteUser(
|
||||
@AuthUser() { id: userId }: User,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
|
||||
@ -1,140 +0,0 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
|
||||
export type UserPayload = {
|
||||
displayName: string | undefined | null;
|
||||
email: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.user.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.user.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.user.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.user.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.user.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.user.create;
|
||||
createMany = this.prismaService.client.user.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.user.update;
|
||||
upsert = this.prismaService.client.user.upsert;
|
||||
updateMany = this.prismaService.client.user.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.user.delete;
|
||||
deleteMany = this.prismaService.client.user.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.user.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.user.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.user.groupBy;
|
||||
|
||||
// Customs
|
||||
async createUser<T extends Prisma.UserCreateArgs>(
|
||||
args: Prisma.SelectSubset<T, Prisma.UserCreateArgs>,
|
||||
workspaceId?: string,
|
||||
): Promise<Prisma.UserGetPayload<T>> {
|
||||
assert(args.data.email, 'email is missing', BadRequestException);
|
||||
|
||||
// Create workspace if not exists
|
||||
const workspace = workspaceId
|
||||
? await this.workspaceService.findUnique({
|
||||
where: {
|
||||
id: workspaceId,
|
||||
},
|
||||
})
|
||||
: await this.workspaceService.createDefaultWorkspace();
|
||||
|
||||
assert(workspace, 'workspace is missing', BadRequestException);
|
||||
// Create user
|
||||
const user = await this.prismaService.client.user.upsert({
|
||||
where: {
|
||||
email: args.data.email,
|
||||
},
|
||||
create: {
|
||||
...(args.data as Prisma.UserCreateInput),
|
||||
defaultWorkspaceId: workspace.id,
|
||||
},
|
||||
update: {},
|
||||
...(args.select ? { select: args.select } : {}),
|
||||
...(args.include ? { include: args.include } : {}),
|
||||
} as Prisma.UserUpsertArgs);
|
||||
|
||||
return user as Prisma.UserGetPayload<T>;
|
||||
}
|
||||
|
||||
async deleteUser({
|
||||
workspaceId,
|
||||
userId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const { workspaceMember, refreshToken } = this.prismaService.client;
|
||||
const user = await this.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
assert(user, 'User not found');
|
||||
|
||||
const workspace = await this.workspaceService.findUnique({
|
||||
where: { id: workspaceId },
|
||||
select: { id: true },
|
||||
});
|
||||
assert(workspace, 'Workspace not found');
|
||||
|
||||
const workSpaceMembers = await workspaceMember.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const isLastMember =
|
||||
workSpaceMembers.length === 1 && workSpaceMembers[0].userId === userId;
|
||||
|
||||
if (isLastMember) {
|
||||
// Delete entire workspace
|
||||
await this.workspaceService.deleteWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
} else {
|
||||
await this.prismaService.client.$transaction([
|
||||
workspaceMember.deleteMany({
|
||||
where: { userId },
|
||||
}),
|
||||
|
||||
refreshToken.deleteMany({
|
||||
where: { userId },
|
||||
}),
|
||||
|
||||
this.delete({ where: { id: userId } }),
|
||||
]);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { WebHookResolver } from 'src/core/web-hook/web-hook.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, AbilityModule],
|
||||
providers: [WebHookResolver],
|
||||
})
|
||||
export class WebHookModule {}
|
||||
@ -1,72 +0,0 @@
|
||||
import { NotFoundException, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
CreateWebHookAbilityHandler,
|
||||
DeleteWebHookAbilityHandler,
|
||||
ReadWebHookAbilityHandler,
|
||||
} from 'src/ability/handlers/web-hook.ability-handler';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { CreateOneWebHookArgs } from 'src/core/@generated/web-hook/create-one-web-hook.args';
|
||||
import { DeleteOneWebHookArgs } from 'src/core/@generated/web-hook/delete-one-web-hook.args';
|
||||
import { FindManyWebHookArgs } from 'src/core/@generated/web-hook/find-many-web-hook.args';
|
||||
import { WebHook } from 'src/core/@generated/web-hook/web-hook.model';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => WebHook)
|
||||
export class WebHookResolver {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
@Mutation(() => WebHook)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateWebHookAbilityHandler)
|
||||
async createOneWebHook(
|
||||
@Args() args: CreateOneWebHookArgs,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<WebHook> {
|
||||
return this.prismaService.client.webHook.create({
|
||||
data: {
|
||||
...args.data,
|
||||
...{ workspace: { connect: { id: workspaceId } } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => WebHook, { nullable: false })
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteWebHookAbilityHandler)
|
||||
async deleteOneWebHook(@Args() args: DeleteOneWebHookArgs): Promise<WebHook> {
|
||||
const hookToDelete = this.prismaService.client.webHook.findUnique({
|
||||
where: args.where,
|
||||
});
|
||||
if (!hookToDelete) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return await this.prismaService.client.webHook.delete({
|
||||
where: args.where,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [WebHook])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadWebHookAbilityHandler)
|
||||
async findManyWebHook(
|
||||
@Args() args: FindManyWebHookArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
) {
|
||||
const filterOptions = [accessibleBy(ability).WorkspaceMember];
|
||||
if (args.where) filterOptions.push(args.where);
|
||||
return this.prismaService.client.webHook.findMany({
|
||||
...args,
|
||||
where: { AND: filterOptions },
|
||||
});
|
||||
}
|
||||
}
|
||||
26
server/src/core/workspace/dtos/update-workspace-input.ts
Normal file
26
server/src/core/workspace/dtos/update-workspace-input.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UpdateWorkspaceInput {
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
domainName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
displayName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
logo?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
inviteHash?: string;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { WorkspaceMemberService } from 'src/core/workspace/services/workspace-member.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { WorkspaceMemberResolver } from './workspace-member.resolver';
|
||||
|
||||
describe('WorkspaceMemberResolver', () => {
|
||||
let resolver: WorkspaceMemberResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WorkspaceMemberResolver,
|
||||
{
|
||||
provide: WorkspaceMemberService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: AbilityFactory,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<WorkspaceMemberResolver>(WorkspaceMemberResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,102 +0,0 @@
|
||||
import { Args, Query, Resolver, Mutation } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { WorkspaceMember } from 'src/core/@generated/workspace-member/workspace-member.model';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
DeleteWorkspaceMemberAbilityHandler,
|
||||
ReadWorkspaceMemberAbilityHandler,
|
||||
UpdateWorkspaceMemberAbilityHandler,
|
||||
} from 'src/ability/handlers/workspace-member.ability-handler';
|
||||
import { FindManyWorkspaceMemberArgs } from 'src/core/@generated/workspace-member/find-many-workspace-member.args';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { WorkspaceMemberService } from 'src/core/workspace/services/workspace-member.service';
|
||||
import { DeleteOneWorkspaceMemberArgs } from 'src/core/@generated/workspace-member/delete-one-workspace-member.args';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||
import { User } from 'src/core/@generated/user/user.model';
|
||||
import { UpdateOneWorkspaceMemberArgs } from 'src/core/@generated/workspace-member/update-one-workspace-member.args';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => WorkspaceMember)
|
||||
export class WorkspaceMemberResolver {
|
||||
constructor(
|
||||
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||
) {}
|
||||
|
||||
@Query(() => [WorkspaceMember])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadWorkspaceMemberAbilityHandler)
|
||||
async findManyWorkspaceMember(
|
||||
@Args() args: FindManyWorkspaceMemberArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'WorkspaceMember' })
|
||||
prismaSelect: PrismaSelect<'WorkspaceMember'>,
|
||||
): Promise<Partial<WorkspaceMember>[]> {
|
||||
return this.workspaceMemberService.findMany({
|
||||
...args,
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).WorkspaceMember],
|
||||
}
|
||||
: accessibleBy(ability).WorkspaceMember,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => WorkspaceMember)
|
||||
async allowImpersonation(
|
||||
@Args('allowImpersonation') allowImpersonation: boolean,
|
||||
@AuthUser() user: User,
|
||||
@PrismaSelector({ modelName: 'WorkspaceMember' })
|
||||
prismaSelect: PrismaSelect<'WorkspaceMember'>,
|
||||
): Promise<Partial<WorkspaceMember>> {
|
||||
return this.workspaceMemberService.update({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
data: {
|
||||
allowImpersonation,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => WorkspaceMember)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteWorkspaceMemberAbilityHandler)
|
||||
async deleteWorkspaceMember(
|
||||
@Args() args: DeleteOneWorkspaceMemberArgs,
|
||||
@PrismaSelector({ modelName: 'WorkspaceMember' })
|
||||
prismaSelect: PrismaSelect<'WorkspaceMember'>,
|
||||
): Promise<Partial<WorkspaceMember>> {
|
||||
return this.workspaceMemberService.delete({
|
||||
where: args.where,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => WorkspaceMember)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateWorkspaceMemberAbilityHandler)
|
||||
async UpdateOneWorkspaceMember(
|
||||
@Args() args: UpdateOneWorkspaceMemberArgs,
|
||||
@PrismaSelector({ modelName: 'WorkspaceMember' })
|
||||
prismaSelect: PrismaSelect<'WorkspaceMember'>,
|
||||
): Promise<Partial<WorkspaceMember>> {
|
||||
return this.workspaceMemberService.update({
|
||||
data: args.data,
|
||||
where: args.where,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.WorkspaceMemberUpdateArgs);
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||
|
||||
import { WorkspaceResolver } from './workspace.resolver';
|
||||
|
||||
describe('WorkspaceResolver', () => {
|
||||
let resolver: WorkspaceResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WorkspaceResolver,
|
||||
{ provide: WorkspaceService, useValue: {} },
|
||||
{ provide: AbilityFactory, useValue: {} },
|
||||
{ provide: FileUploadService, useValue: {} },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<WorkspaceResolver>(WorkspaceResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,115 +0,0 @@
|
||||
import { Query, Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { WorkspaceUpdateInput } from 'src/core/@generated/workspace/workspace-update.input';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
UpdateWorkspaceAbilityHandler,
|
||||
DeleteWorkspaceAbilityHandler,
|
||||
} from 'src/ability/handlers/workspace.ability-handler';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Workspace)
|
||||
export class WorkspaceResolver {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
) {}
|
||||
|
||||
@Mutation(() => Workspace)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateWorkspaceAbilityHandler)
|
||||
async updateWorkspace(
|
||||
@Args('data') data: WorkspaceUpdateInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Workspace' })
|
||||
prismaSelect: PrismaSelect<'Workspace'>,
|
||||
) {
|
||||
return this.workspaceService.update({
|
||||
where: {
|
||||
id: workspace.id,
|
||||
},
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.WorkspaceUpdateArgs);
|
||||
}
|
||||
|
||||
@Query(() => Workspace)
|
||||
async currentWorkspace(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@PrismaSelector({ modelName: 'Workspace' })
|
||||
prismaSelect: PrismaSelect<'Workspace'>,
|
||||
) {
|
||||
const selectedWorkspace = await this.workspaceService.findUnique({
|
||||
where: {
|
||||
id: workspace.id,
|
||||
},
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
assert(selectedWorkspace, 'User not found');
|
||||
|
||||
return selectedWorkspace;
|
||||
}
|
||||
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateWorkspaceAbilityHandler)
|
||||
@Mutation(() => String)
|
||||
async uploadWorkspaceLogo(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
): Promise<string> {
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
const fileFolder = FileFolder.WorkspaceLogo;
|
||||
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
});
|
||||
|
||||
await this.workspaceService.update({
|
||||
where: { id: workspace.id },
|
||||
data: {
|
||||
logo: paths[0],
|
||||
},
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteWorkspaceAbilityHandler)
|
||||
@Mutation(() => Workspace)
|
||||
async deleteCurrentWorkspace(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@PrismaSelector({ modelName: 'Workspace' })
|
||||
{ value: select }: PrismaSelect<'Workspace'>,
|
||||
) {
|
||||
return this.workspaceService.deleteWorkspace({
|
||||
workspaceId,
|
||||
select,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
|
||||
import { WorkspaceMemberService } from './workspace-member.service';
|
||||
|
||||
describe('WorkspaceMemberService', () => {
|
||||
let service: WorkspaceMemberService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WorkspaceMemberService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WorkspaceMemberService>(WorkspaceMemberService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,40 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMemberService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.workspaceMember.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.workspaceMember.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.workspaceMember.findUnique;
|
||||
findUniqueOrThrow =
|
||||
this.prismaService.client.workspaceMember.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.workspaceMember.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.workspaceMember.create;
|
||||
createMany = this.prismaService.client.workspaceMember.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.workspaceMember.update;
|
||||
upsert = this.prismaService.client.workspaceMember.upsert;
|
||||
updateMany = this.prismaService.client.workspaceMember.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.workspaceMember.delete;
|
||||
deleteMany = this.prismaService.client.workspaceMember.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.workspaceMember.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.workspaceMember.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.workspaceMember.groupBy;
|
||||
}
|
||||
@ -1,13 +1,7 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||
import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
||||
import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service';
|
||||
import { PersonService } from 'src/core/person/person.service';
|
||||
import { CompanyService } from 'src/core/company/company.service';
|
||||
import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service';
|
||||
import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspace-manager.service';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
|
||||
import { WorkspaceService } from './workspace.service';
|
||||
|
||||
@ -19,31 +13,7 @@ describe('WorkspaceService', () => {
|
||||
providers: [
|
||||
WorkspaceService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prismaMock,
|
||||
},
|
||||
{
|
||||
provide: PipelineService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: PipelineStageService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: PersonService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: CompanyService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: PipelineProgressService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceManagerService,
|
||||
provide: getRepositoryToken(Workspace),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
|
||||
@ -1,167 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { v4 } from 'uuid';
|
||||
import assert from 'assert';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { CompanyService } from 'src/core/company/company.service';
|
||||
import { PersonService } from 'src/core/person/person.service';
|
||||
import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service';
|
||||
import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service';
|
||||
import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspace-manager.service';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceService {
|
||||
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly pipelineService: PipelineService,
|
||||
private readonly companyService: CompanyService,
|
||||
private readonly personService: PersonService,
|
||||
private readonly pipelineStageService: PipelineStageService,
|
||||
private readonly pipelineProgressService: PipelineProgressService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly workspaceManagerService: WorkspaceManagerService,
|
||||
) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.workspace.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.workspace.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.workspace.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.workspace.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.workspace.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.workspace.create;
|
||||
createMany = this.prismaService.client.workspace.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.workspace.update;
|
||||
upsert = this.prismaService.client.workspace.upsert;
|
||||
updateMany = this.prismaService.client.workspace.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.workspace.delete;
|
||||
deleteMany = this.prismaService.client.workspace.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.workspace.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.workspace.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.workspace.groupBy;
|
||||
|
||||
// Customs
|
||||
async createDefaultWorkspace() {
|
||||
const workspace = await this.create({
|
||||
data: {
|
||||
inviteHash: v4(),
|
||||
},
|
||||
});
|
||||
|
||||
// Create workspace schema
|
||||
await this.workspaceManagerService.init(workspace.id);
|
||||
|
||||
// Create default companies
|
||||
const companies = await this.companyService.createDefaultCompanies({
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
// Create default people
|
||||
await this.personService.createDefaultPeople({
|
||||
workspaceId: workspace.id,
|
||||
companies,
|
||||
});
|
||||
|
||||
// Create default pipeline
|
||||
const pipeline = await this.pipelineService.createDefaultPipeline({
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
// Create default stages
|
||||
await this.pipelineStageService.createDefaultPipelineStages({
|
||||
pipelineId: pipeline.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return workspace;
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async deleteWorkspace({
|
||||
workspaceId,
|
||||
select = { id: true },
|
||||
}: {
|
||||
workspaceId: string;
|
||||
select?: Prisma.WorkspaceSelect;
|
||||
}) {
|
||||
const workspace = await this.findUnique({
|
||||
where: { id: workspaceId },
|
||||
select,
|
||||
});
|
||||
async deleteWorkspace(id: string) {
|
||||
const workspace = await this.workspaceRepository.findOneBy({ id });
|
||||
assert(workspace, 'Workspace not found');
|
||||
|
||||
const where = { workspaceId };
|
||||
|
||||
const {
|
||||
workspaceMember,
|
||||
attachment,
|
||||
comment,
|
||||
activityTarget,
|
||||
activity,
|
||||
apiKey,
|
||||
favorite,
|
||||
webHook,
|
||||
} = this.prismaService.client;
|
||||
|
||||
// We don't delete user or refresh tokens as they can belong to another workspace
|
||||
await this.prismaService.client.$transaction([
|
||||
this.pipelineProgressService.deleteMany({
|
||||
where,
|
||||
}),
|
||||
this.companyService.deleteMany({
|
||||
where,
|
||||
}),
|
||||
this.personService.deleteMany({
|
||||
where,
|
||||
}),
|
||||
this.pipelineStageService.deleteMany({
|
||||
where,
|
||||
}),
|
||||
this.pipelineService.deleteMany({
|
||||
where,
|
||||
}),
|
||||
workspaceMember.deleteMany({
|
||||
where,
|
||||
}),
|
||||
attachment.deleteMany({
|
||||
where,
|
||||
}),
|
||||
comment.deleteMany({
|
||||
where,
|
||||
}),
|
||||
activityTarget.deleteMany({
|
||||
where,
|
||||
}),
|
||||
activity.deleteMany({
|
||||
where,
|
||||
}),
|
||||
apiKey.deleteMany({
|
||||
where,
|
||||
}),
|
||||
favorite.deleteMany({
|
||||
where,
|
||||
}),
|
||||
webHook.deleteMany({
|
||||
where,
|
||||
}),
|
||||
this.delete({ where: { id: workspaceId } }),
|
||||
]);
|
||||
|
||||
await this.workspaceManagerService.delete(workspaceId);
|
||||
await this.workspaceManagerService.delete(id);
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
41
server/src/core/workspace/workspace.auto-resolver-opts.ts
Normal file
41
server/src/core/workspace/workspace.auto-resolver-opts.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { UpdateWorkspaceInput } from 'src/core/workspace/dtos/update-workspace-input';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
|
||||
export const workspaceAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: Workspace,
|
||||
DTOClass: Workspace,
|
||||
UpdateDTOClass: UpdateWorkspaceInput,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
one: { disabled: true },
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
52
server/src/core/workspace/workspace.entity.ts
Normal file
52
server/src/core/workspace/workspace.entity.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
|
||||
@Entity({ name: 'workspace', schema: 'core' })
|
||||
@ObjectType('Workspace')
|
||||
export class Workspace {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
domainName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
displayName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
logo?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
inviteHash?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
deletedAt?: Date;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
||||
updatedAt: Date;
|
||||
|
||||
@OneToMany(() => User, (user) => user.defaultWorkspace)
|
||||
users: User[];
|
||||
}
|
||||
@ -1,34 +1,35 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { PipelineModule } from 'src/core/pipeline/pipeline.module';
|
||||
import { CompanyModule } from 'src/core/company/company.module';
|
||||
import { PersonModule } from 'src/core/person/person.module';
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FileModule } from 'src/core/file/file.module';
|
||||
import { WorkspaceManagerModule } from 'src/workspace/workspace-manager/workspace-manager.module';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
import { WorkspaceResolver } from 'src/core/workspace/workspace.resolver';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import config from '../../../ormconfig';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
||||
|
||||
import { WorkspaceService } from './services/workspace.service';
|
||||
import { WorkspaceMemberService } from './services/workspace-member.service';
|
||||
import { WorkspaceMemberResolver } from './resolvers/workspace-member.resolver';
|
||||
import { WorkspaceResolver } from './resolvers/workspace.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AbilityModule,
|
||||
PipelineModule,
|
||||
CompanyModule,
|
||||
PersonModule,
|
||||
WorkspaceManagerModule,
|
||||
PrismaModule,
|
||||
TypeOrmModule.forRoot(config),
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([Workspace]),
|
||||
WorkspaceManagerModule,
|
||||
FileModule,
|
||||
],
|
||||
services: [WorkspaceService],
|
||||
resolvers: workspaceAutoResolverOpts,
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
WorkspaceService,
|
||||
FileUploadService,
|
||||
WorkspaceMemberService,
|
||||
WorkspaceMemberResolver,
|
||||
WorkspaceResolver,
|
||||
],
|
||||
exports: [WorkspaceService, WorkspaceMemberService],
|
||||
exports: [WorkspaceService],
|
||||
providers: [WorkspaceResolver, WorkspaceService],
|
||||
})
|
||||
export class WorkspaceModule {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user