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:
Charles Bochet
2023-11-19 18:25:47 +01:00
committed by GitHub
parent 18dac1a2b6
commit f5e1d7825a
616 changed files with 2220 additions and 23073 deletions

View File

@ -1,7 +0,0 @@
export enum AbilityAction {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}

View File

@ -1,176 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PureAbility, AbilityBuilder } from '@casl/ability';
import { createPrismaAbility, PrismaQuery, Subjects } from '@casl/prisma';
import {
Activity,
ActivityTarget,
Attachment,
ApiKey,
Comment,
Company,
Favorite,
Person,
Pipeline,
PipelineProgress,
PipelineStage,
RefreshToken,
User,
UserSettings,
WebHook,
Workspace,
WorkspaceMember,
} from '@prisma/client';
import { AbilityAction } from './ability.action';
type SubjectsAbility = Subjects<{
Activity: Activity;
ActivityTarget: ActivityTarget;
Attachment: Attachment;
ApiKey: ApiKey;
Comment: Comment;
Company: Company;
Favorite: Favorite;
WebHook: WebHook;
Person: Person;
Pipeline: Pipeline;
PipelineProgress: PipelineProgress;
PipelineStage: PipelineStage;
RefreshToken: RefreshToken;
User: User;
UserSettings: UserSettings;
Workspace: Workspace;
WorkspaceMember: WorkspaceMember;
}>;
export type AppAbility = PureAbility<
[string, SubjectsAbility | 'all'],
PrismaQuery
>;
@Injectable()
export class AbilityFactory {
defineAbility(workspace: Workspace, user?: User) {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(
createPrismaAbility,
);
// User
if (user) {
can(AbilityAction.Read, 'User', { id: user.id });
can(AbilityAction.Update, 'User', { id: user.id });
can(AbilityAction.Delete, 'User', { id: user.id });
} else {
cannot(AbilityAction.Update, 'User');
cannot(AbilityAction.Delete, 'User');
}
// ApiKey
can(AbilityAction.Read, 'ApiKey', { workspaceId: workspace.id });
can(AbilityAction.Create, 'ApiKey');
can(AbilityAction.Update, 'ApiKey', { workspaceId: workspace.id });
// WebHook
can(AbilityAction.Read, 'WebHook', { workspaceId: workspace.id });
can(AbilityAction.Create, 'WebHook');
can(AbilityAction.Delete, 'WebHook', { workspaceId: workspace.id });
// Workspace
can(AbilityAction.Read, 'Workspace');
can(AbilityAction.Update, 'Workspace');
can(AbilityAction.Delete, 'Workspace');
// Workspace Member
can(AbilityAction.Read, 'WorkspaceMember', { workspaceId: workspace.id });
if (user) {
can(AbilityAction.Delete, 'WorkspaceMember', {
workspaceId: workspace.id,
});
cannot(AbilityAction.Delete, 'WorkspaceMember', { userId: user.id });
can(AbilityAction.Update, 'WorkspaceMember', {
userId: user.id,
workspaceId: workspace.id,
});
} else {
cannot(AbilityAction.Delete, 'WorkspaceMember');
cannot(AbilityAction.Update, 'WorkspaceMember');
}
// Company
can(AbilityAction.Read, 'Company', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Company');
can(AbilityAction.Update, 'Company', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'Company', { workspaceId: workspace.id });
// Person
can(AbilityAction.Read, 'Person', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Person');
can(AbilityAction.Update, 'Person', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'Person', { workspaceId: workspace.id });
// RefreshToken
cannot(AbilityAction.Manage, 'RefreshToken');
// Activity
can(AbilityAction.Read, 'Activity', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Activity');
can(AbilityAction.Update, 'Activity', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'Activity', { workspaceId: workspace.id });
// Comment
can(AbilityAction.Read, 'Comment', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Comment');
if (user) {
can(AbilityAction.Update, 'Comment', {
workspaceId: workspace.id,
authorId: user.id,
});
can(AbilityAction.Delete, 'Comment', {
workspaceId: workspace.id,
authorId: user.id,
});
} else {
cannot(AbilityAction.Update, 'Comment');
cannot(AbilityAction.Delete, 'Comment');
}
// ActivityTarget
can(AbilityAction.Read, 'ActivityTarget');
can(AbilityAction.Create, 'ActivityTarget');
// Attachment
can(AbilityAction.Read, 'Attachment', { workspaceId: workspace.id });
can(AbilityAction.Update, 'Attachment', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Attachment', { workspaceId: workspace.id });
// Pipeline
can(AbilityAction.Read, 'Pipeline', { workspaceId: workspace.id });
// PipelineStage
can(AbilityAction.Read, 'PipelineStage', { workspaceId: workspace.id });
can(AbilityAction.Create, 'PipelineStage', { workspaceId: workspace.id });
can(AbilityAction.Update, 'PipelineStage', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'PipelineStage', { workspaceId: workspace.id });
// PipelineProgress
can(AbilityAction.Read, 'PipelineProgress', { workspaceId: workspace.id });
can(AbilityAction.Create, 'PipelineProgress');
can(AbilityAction.Update, 'PipelineProgress', {
workspaceId: workspace.id,
});
can(AbilityAction.Delete, 'PipelineProgress', {
workspaceId: workspace.id,
});
// Favorite
can(AbilityAction.Read, 'Favorite', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Favorite');
can(AbilityAction.Update, 'Favorite', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'Favorite', { workspaceId: workspace.id });
return build();
}
}

View File

@ -1,309 +0,0 @@
import { Module } from '@nestjs/common';
import { AbilityFactory } from 'src/ability/ability.factory';
import { PrismaService } from 'src/database/prisma.service';
import {
CreateWebHookAbilityHandler,
DeleteWebHookAbilityHandler,
ReadWebHookAbilityHandler,
} from 'src/ability/handlers/web-hook.ability-handler';
import {
CreateUserAbilityHandler,
DeleteUserAbilityHandler,
ManageUserAbilityHandler,
ReadUserAbilityHandler,
UpdateUserAbilityHandler,
} from './handlers/user.ability-handler';
import {
CreateWorkspaceAbilityHandler,
DeleteWorkspaceAbilityHandler,
ManageWorkspaceAbilityHandler,
ReadWorkspaceAbilityHandler,
UpdateWorkspaceAbilityHandler,
} from './handlers/workspace.ability-handler';
import {
CreateWorkspaceMemberAbilityHandler,
DeleteWorkspaceMemberAbilityHandler,
ManageWorkspaceMemberAbilityHandler,
ReadWorkspaceMemberAbilityHandler,
UpdateWorkspaceMemberAbilityHandler,
} from './handlers/workspace-member.ability-handler';
import {
ManageCompanyAbilityHandler,
ReadOneCompanyAbilityHandler,
CreateCompanyAbilityHandler,
UpdateCompanyAbilityHandler,
DeleteCompanyAbilityHandler,
} from './handlers/company.ability-handler';
import {
CreatePersonAbilityHandler,
DeletePersonAbilityHandler,
ManagePersonAbilityHandler,
ReadPersonAbilityHandler,
UpdatePersonAbilityHandler,
} from './handlers/person.ability-handler';
import {
ManageRefreshTokenAbilityHandler,
ReadRefreshTokenAbilityHandler,
CreateRefreshTokenAbilityHandler,
UpdateRefreshTokenAbilityHandler,
DeleteRefreshTokenAbilityHandler,
} from './handlers/refresh-token.ability-handler';
import {
ManageActivityAbilityHandler,
ReadActivityAbilityHandler,
CreateActivityAbilityHandler,
UpdateActivityAbilityHandler,
DeleteActivityAbilityHandler,
} from './handlers/activity.ability-handler';
import {
ManageCommentAbilityHandler,
ReadCommentAbilityHandler,
CreateCommentAbilityHandler,
UpdateCommentAbilityHandler,
DeleteCommentAbilityHandler,
} from './handlers/comment.ability-handler';
import {
ManageActivityTargetAbilityHandler,
ReadActivityTargetAbilityHandler,
CreateActivityTargetAbilityHandler,
UpdateActivityTargetAbilityHandler,
DeleteActivityTargetAbilityHandler,
} from './handlers/activity-target.ability-handler';
import {
ManagePipelineAbilityHandler,
ReadPipelineAbilityHandler,
CreatePipelineAbilityHandler,
UpdatePipelineAbilityHandler,
DeletePipelineAbilityHandler,
} from './handlers/pipeline.ability-handler';
import {
ManagePipelineStageAbilityHandler,
ReadPipelineStageAbilityHandler,
CreatePipelineStageAbilityHandler,
UpdatePipelineStageAbilityHandler,
DeletePipelineStageAbilityHandler,
} from './handlers/pipeline-stage.ability-handler';
import {
ManagePipelineProgressAbilityHandler,
ReadPipelineProgressAbilityHandler,
CreatePipelineProgressAbilityHandler,
UpdatePipelineProgressAbilityHandler,
DeletePipelineProgressAbilityHandler,
} from './handlers/pipeline-progress.ability-handler';
import {
CreateAttachmentAbilityHandler,
DeleteAttachmentAbilityHandler,
ManageAttachmentAbilityHandler,
ReadAttachmentAbilityHandler,
UpdateAttachmentAbilityHandler,
} from './handlers/attachment.ability-handler';
import {
CreateFavoriteAbilityHandler,
ReadFavoriteAbilityHandler,
DeleteFavoriteAbilityHandler,
UpdateFavoriteAbilityHandler,
} from './handlers/favorite.ability-handler';
import {
CreateApiKeyAbilityHandler,
UpdateApiKeyAbilityHandler,
ManageApiKeyAbilityHandler,
ReadApiKeyAbilityHandler,
} from './handlers/api-key.ability-handler';
@Module({
providers: [
AbilityFactory,
PrismaService,
// User
ManageUserAbilityHandler,
ReadUserAbilityHandler,
CreateUserAbilityHandler,
UpdateUserAbilityHandler,
DeleteUserAbilityHandler,
// Workspace
ManageWorkspaceAbilityHandler,
ReadWorkspaceAbilityHandler,
CreateWorkspaceAbilityHandler,
UpdateWorkspaceAbilityHandler,
DeleteWorkspaceAbilityHandler,
// Workspace Member
ManageWorkspaceMemberAbilityHandler,
ReadWorkspaceMemberAbilityHandler,
CreateWorkspaceMemberAbilityHandler,
UpdateWorkspaceMemberAbilityHandler,
DeleteWorkspaceMemberAbilityHandler,
// Company
ManageCompanyAbilityHandler,
ReadOneCompanyAbilityHandler,
CreateCompanyAbilityHandler,
UpdateCompanyAbilityHandler,
DeleteCompanyAbilityHandler,
// Person
ManagePersonAbilityHandler,
ReadPersonAbilityHandler,
CreatePersonAbilityHandler,
UpdatePersonAbilityHandler,
DeletePersonAbilityHandler,
// RefreshToken
ManageRefreshTokenAbilityHandler,
ReadRefreshTokenAbilityHandler,
CreateRefreshTokenAbilityHandler,
UpdateRefreshTokenAbilityHandler,
DeleteRefreshTokenAbilityHandler,
// Activity
ManageActivityAbilityHandler,
ReadActivityAbilityHandler,
CreateActivityAbilityHandler,
UpdateActivityAbilityHandler,
DeleteActivityAbilityHandler,
// Comment
ManageCommentAbilityHandler,
ReadCommentAbilityHandler,
CreateCommentAbilityHandler,
UpdateCommentAbilityHandler,
DeleteCommentAbilityHandler,
// ActivityTarget
ManageActivityTargetAbilityHandler,
ReadActivityTargetAbilityHandler,
CreateActivityTargetAbilityHandler,
UpdateActivityTargetAbilityHandler,
DeleteActivityTargetAbilityHandler,
//Attachment
ManageAttachmentAbilityHandler,
ReadAttachmentAbilityHandler,
CreateAttachmentAbilityHandler,
UpdateAttachmentAbilityHandler,
DeleteAttachmentAbilityHandler,
// Pipeline
ManagePipelineAbilityHandler,
ReadPipelineAbilityHandler,
CreatePipelineAbilityHandler,
UpdatePipelineAbilityHandler,
DeletePipelineAbilityHandler,
// PipelineStage
ManagePipelineStageAbilityHandler,
ReadPipelineStageAbilityHandler,
CreatePipelineStageAbilityHandler,
UpdatePipelineStageAbilityHandler,
DeletePipelineStageAbilityHandler,
// PipelineProgress
ManagePipelineProgressAbilityHandler,
ReadPipelineProgressAbilityHandler,
CreatePipelineProgressAbilityHandler,
UpdatePipelineProgressAbilityHandler,
DeletePipelineProgressAbilityHandler,
//Favorite
ReadFavoriteAbilityHandler,
CreateFavoriteAbilityHandler,
UpdateFavoriteAbilityHandler,
DeleteFavoriteAbilityHandler,
// ApiKey
ReadApiKeyAbilityHandler,
ManageApiKeyAbilityHandler,
CreateApiKeyAbilityHandler,
UpdateApiKeyAbilityHandler,
// Hook
CreateWebHookAbilityHandler,
DeleteWebHookAbilityHandler,
ReadWebHookAbilityHandler,
],
exports: [
AbilityFactory,
// User
ManageUserAbilityHandler,
ReadUserAbilityHandler,
CreateUserAbilityHandler,
UpdateUserAbilityHandler,
DeleteUserAbilityHandler,
// Workspace
ManageWorkspaceAbilityHandler,
ReadWorkspaceAbilityHandler,
CreateWorkspaceAbilityHandler,
UpdateWorkspaceAbilityHandler,
DeleteWorkspaceAbilityHandler,
// Workspace Member
ManageWorkspaceMemberAbilityHandler,
ReadWorkspaceMemberAbilityHandler,
CreateWorkspaceMemberAbilityHandler,
UpdateWorkspaceMemberAbilityHandler,
DeleteWorkspaceMemberAbilityHandler,
// Company
ManageCompanyAbilityHandler,
ReadOneCompanyAbilityHandler,
CreateCompanyAbilityHandler,
UpdateCompanyAbilityHandler,
DeleteCompanyAbilityHandler,
// Person
ManagePersonAbilityHandler,
ReadPersonAbilityHandler,
CreatePersonAbilityHandler,
UpdatePersonAbilityHandler,
DeletePersonAbilityHandler,
// RefreshToken
ManageRefreshTokenAbilityHandler,
ReadRefreshTokenAbilityHandler,
CreateRefreshTokenAbilityHandler,
UpdateRefreshTokenAbilityHandler,
DeleteRefreshTokenAbilityHandler,
// Activity
ManageActivityAbilityHandler,
ReadActivityAbilityHandler,
CreateActivityAbilityHandler,
UpdateActivityAbilityHandler,
DeleteActivityAbilityHandler,
// Comment
ManageCommentAbilityHandler,
ReadCommentAbilityHandler,
CreateCommentAbilityHandler,
UpdateCommentAbilityHandler,
DeleteCommentAbilityHandler,
// ActivityTarget
ManageActivityTargetAbilityHandler,
ReadActivityTargetAbilityHandler,
CreateActivityTargetAbilityHandler,
UpdateActivityTargetAbilityHandler,
DeleteActivityTargetAbilityHandler,
//Attachment
ManageAttachmentAbilityHandler,
ReadAttachmentAbilityHandler,
CreateAttachmentAbilityHandler,
UpdateAttachmentAbilityHandler,
DeleteAttachmentAbilityHandler,
// Pipeline
ManagePipelineAbilityHandler,
ReadPipelineAbilityHandler,
CreatePipelineAbilityHandler,
UpdatePipelineAbilityHandler,
DeletePipelineAbilityHandler,
// PipelineStage
ManagePipelineStageAbilityHandler,
ReadPipelineStageAbilityHandler,
CreatePipelineStageAbilityHandler,
UpdatePipelineStageAbilityHandler,
DeletePipelineStageAbilityHandler,
// PipelineProgress
ManagePipelineProgressAbilityHandler,
ReadPipelineProgressAbilityHandler,
CreatePipelineProgressAbilityHandler,
UpdatePipelineProgressAbilityHandler,
DeletePipelineProgressAbilityHandler,
//Favorite
ReadFavoriteAbilityHandler,
CreateFavoriteAbilityHandler,
DeleteFavoriteAbilityHandler,
// ApiKey
ReadApiKeyAbilityHandler,
ManageApiKeyAbilityHandler,
CreateApiKeyAbilityHandler,
UpdateApiKeyAbilityHandler,
// Hook
CreateWebHookAbilityHandler,
DeleteWebHookAbilityHandler,
ReadWebHookAbilityHandler,
],
})
export class AbilityModule {}

View File

@ -1,253 +0,0 @@
import { Prisma, PrismaClient } from '@prisma/client';
import { subject } from '@casl/ability';
import { camelCase } from 'src/utils/camel-case';
import { AppAbility } from './ability.factory';
import { AbilityAction } from './ability.action';
type OperationType =
| 'create'
| 'connectOrCreate'
| 'upsert'
| 'createMany'
| 'set'
| 'disconnect'
| 'delete'
| 'connect'
| 'update'
| 'updateMany'
| 'deleteMany';
// in most case unique identifier is the id, but it can be something else...
type OperationAbilityChecker = (
modelName: Prisma.ModelName,
ability: AppAbility,
prisma: PrismaClient,
data: any,
) => Promise<boolean>;
const createAbilityCheck: OperationAbilityChecker = async (
modelName,
ability,
prisma,
data,
) => {
// Handle all operations cases
const items = data?.data
? !Array.isArray(data.data)
? [data.data]
: data.data
: !Array.isArray(data)
? [data]
: data;
// Check if user try to create an element that is not allowed to create
for (const {} of items) {
if (!ability.can(AbilityAction.Create, modelName)) {
return false;
}
}
return true;
};
const simpleAbilityCheck: OperationAbilityChecker = async (
modelName,
ability,
prisma,
data,
) => {
// TODO: Replace user by workspaceMember and remove this check
if (
modelName === 'User' ||
modelName === 'UserSettings' ||
modelName === 'Workspace'
) {
return true;
}
if (typeof data === 'boolean') {
return true;
}
// Extract entity name from model name
const entity = camelCase(modelName);
//TODO: Fix boolean data types so that disconnects are possible
if (typeof data === 'boolean') {
return true;
}
// Handle all operations cases
const operations = !Array.isArray(data) ? [data] : data;
// Handle where case
const normalizedOperations = operations.map((op) =>
op.where ? op.where : op,
);
// Force entity type because of Prisma typing
const items = await prisma[entity as string].findMany({
where: {
OR: normalizedOperations,
},
});
// Check if user try to connect an element that is not allowed to read
for (const item of items) {
if (!ability.can(AbilityAction.Read, subject(modelName, item))) {
return false;
}
}
return true;
};
const operationAbilityCheckers: Record<OperationType, OperationAbilityChecker> =
{
create: createAbilityCheck,
createMany: createAbilityCheck,
upsert: simpleAbilityCheck,
update: simpleAbilityCheck,
updateMany: simpleAbilityCheck,
delete: simpleAbilityCheck,
deleteMany: simpleAbilityCheck,
connectOrCreate: simpleAbilityCheck,
connect: simpleAbilityCheck,
disconnect: simpleAbilityCheck,
set: simpleAbilityCheck,
};
// Check relation nested abilities
export const relationAbilityChecker = async (
modelName: Prisma.ModelName,
ability: AppAbility,
prisma: PrismaClient,
args: any,
) => {
// Extract models from Prisma
const models = Prisma.dmmf.datamodel.models;
// Find main model from options
const mainModel = models.find((item) => item.name === modelName);
if (!mainModel) {
throw new Error('Main model not found');
}
// Loop over fields
for (const field of mainModel.fields) {
// Check if field is a relation
if (field.relationName) {
// Check if field is in args
const operation = args.data?.[field.name] ?? args?.[field.name];
if (operation) {
// Extract operation name and value
const operationType = Object.keys(operation)[0] as OperationType;
const operationValue = operation[operationType];
// Get operation checker for the operation type
const operationChecker = operationAbilityCheckers[operationType];
if (!operationChecker) {
throw new Error('Operation not found');
}
// Check if operation is allowed
const allowed = await operationChecker(
field.type as Prisma.ModelName,
ability,
prisma,
operationValue,
);
if (!allowed) {
return false;
}
// For the 'create', 'connectOrCreate', 'upsert', 'update', and 'updateMany' operations,
// we should also check the nested operations.
if (
[
'create',
'connectOrCreate',
'upsert',
'update',
'updateMany',
].includes(operationType)
) {
// Handle nested operations all cases
const operationValues = !Array.isArray(operationValue)
? [operationValue]
: operationValue;
// Loop over nested args
for (const nestedArgs of operationValues) {
const nestedCreateAllowed = await relationAbilityChecker(
field.type as Prisma.ModelName,
ability,
prisma,
nestedArgs.create ?? nestedArgs.data ?? nestedArgs,
);
if (!nestedCreateAllowed) {
return false;
}
if (nestedArgs.update) {
const nestedUpdateAllowed = await relationAbilityChecker(
field.type as Prisma.ModelName,
ability,
prisma,
nestedArgs.update,
);
if (!nestedUpdateAllowed) {
return false;
}
}
}
}
}
}
}
return true;
};
const isWhereInput = (input: any): boolean => {
return Object.values(input).some((value) => typeof value === 'object');
};
type ExcludeUnique<T> = T extends infer U
? 'AND' extends keyof U
? U
: never
: never;
/**
* Convert a where unique input to a where input prisma
* @param args Can be a where unique input or a where input
* @returns whare input
*/
export const convertToWhereInput = <T>(
where: T | undefined,
): ExcludeUnique<T> | undefined => {
const input = where as any;
if (!input) {
return input;
}
// If it's already a WhereInput, return it directly
if (isWhereInput(input)) {
return input;
}
// If not convert it to a WhereInput
const whereInput = {};
for (const key in input) {
whereInput[key] = { equals: input[key] };
}
return whereInput as ExcludeUnique<T>;
};

View File

@ -1,81 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { assert } from 'src/utils/assert';
import { ActivityTargetWhereInput } from 'src/core/@generated/activity-target/activity-target-where.input';
class ActivityTargetArgs {
where?: ActivityTargetWhereInput;
}
@Injectable()
export class ManageActivityTargetAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'ActivityTarget');
}
}
@Injectable()
export class ReadActivityTargetAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'ActivityTarget');
}
}
@Injectable()
export class CreateActivityTargetAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Create, 'ActivityTarget');
}
}
@Injectable()
export class UpdateActivityTargetAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityTargetArgs>();
const ActivityTarget =
await this.prismaService.client.activityTarget.findFirst({
where: args.where,
});
assert(ActivityTarget, '', NotFoundException);
return ability.can(
AbilityAction.Update,
subject('ActivityTarget', ActivityTarget),
);
}
}
@Injectable()
export class DeleteActivityTargetAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityTargetArgs>();
const ActivityTarget =
await this.prismaService.client.activityTarget.findFirst({
where: args.where,
});
assert(ActivityTarget, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('ActivityTarget', ActivityTarget),
);
}
}

View File

@ -1,73 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { assert } from 'src/utils/assert';
import { ActivityWhereInput } from 'src/core/@generated/activity/activity-where.input';
class ActivityArgs {
where?: ActivityWhereInput;
}
@Injectable()
export class ManageActivityAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Activity');
}
}
@Injectable()
export class ReadActivityAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Activity');
}
}
@Injectable()
export class CreateActivityAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Create, 'Activity');
}
}
@Injectable()
export class UpdateActivityAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityArgs>();
const Activity = await this.prismaService.client.activity.findFirst({
where: args.where,
});
assert(Activity, '', NotFoundException);
return ability.can(AbilityAction.Update, subject('Activity', Activity));
}
}
@Injectable()
export class DeleteActivityAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityArgs>();
const Activity = await this.prismaService.client.activity.findFirst({
where: args.where,
});
assert(Activity, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('Activity', Activity));
}
}

View File

@ -1,85 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { AppAbility } from 'src/ability/ability.factory';
import { AbilityAction } from 'src/ability/ability.action';
import { PrismaService } from 'src/database/prisma.service';
import { ApiKeyWhereUniqueInput } from 'src/core/@generated/api-key/api-key-where-unique.input';
import { ApiKeyWhereInput } from 'src/core/@generated/api-key/api-key-where.input';
import { assert } from 'src/utils/assert';
import {
convertToWhereInput,
relationAbilityChecker,
} from 'src/ability/ability.util';
class ApiKeyArgs {
where?: ApiKeyWhereUniqueInput | ApiKeyWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageApiKeyAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'ApiKey');
}
}
@Injectable()
export class ReadApiKeyAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'ApiKey');
}
}
@Injectable()
export class CreateApiKeyAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'ApiKey',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'ApiKey');
}
}
@Injectable()
export class UpdateApiKeyAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ApiKeyArgs>();
const where = convertToWhereInput(args.where);
const apiKey = await this.prismaService.client.apiKey.findFirst({
where,
});
assert(apiKey, '', NotFoundException);
const allowed = await relationAbilityChecker(
'ApiKey',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, subject('ApiKey', apiKey));
}
}

View File

@ -1,66 +0,0 @@
import {
ExecutionContext,
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { assert } from 'src/utils/assert';
class AttachmentArgs {
activityId?: string;
}
@Injectable()
export class ManageAttachmentAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Attachment');
}
}
@Injectable()
export class ReadAttachmentAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Attachment');
}
}
@Injectable()
export class CreateAttachmentAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<AttachmentArgs>();
assert(args.activityId, '', ForbiddenException);
const activity = await this.prismaService.client.activity.findUnique({
where: { id: args.activityId },
});
assert(activity, '', NotFoundException);
return ability.can(AbilityAction.Update, subject('Activity', activity));
}
}
@Injectable()
export class UpdateAttachmentAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Update, 'Attachment');
}
}
@Injectable()
export class DeleteAttachmentAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Delete, 'Attachment');
}
}

View File

@ -1,102 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { CommentWhereInput } from 'src/core/@generated/comment/comment-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class CommentArgs {
where?: CommentWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageCommentAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Comment');
}
}
@Injectable()
export class ReadCommentAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Comment');
}
}
@Injectable()
export class CreateCommentAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'Comment',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'Comment');
}
}
@Injectable()
export class UpdateCommentAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CommentArgs>();
const comment = await this.prismaService.client.comment.findFirst({
where: args.where,
});
assert(comment, '', NotFoundException);
const allowed = await relationAbilityChecker(
'Comment',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, subject('Comment', comment));
}
}
@Injectable()
export class DeleteCommentAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CommentArgs>();
const comment = await this.prismaService.client.comment.findFirst({
where: args.where,
});
assert(comment, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('Comment', comment));
}
}

View File

@ -1,139 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { CompanyWhereInput } from 'src/core/@generated/company/company-where.input';
import { CompanyWhereUniqueInput } from 'src/core/@generated/company/company-where-unique.input';
import {
convertToWhereInput,
relationAbilityChecker,
} from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class CompanyArgs {
where?: CompanyWhereUniqueInput | CompanyWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageCompanyAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Company');
}
}
@Injectable()
export class ReadOneCompanyAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CompanyArgs>();
const company = await this.prismaService.client.company.findFirst({
where: args.where,
});
assert(company, '', NotFoundException);
return ability.can(AbilityAction.Read, subject('Company', company));
}
}
@Injectable()
export class CreateCompanyAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'Company',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'Company');
}
}
@Injectable()
export class UpdateCompanyAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CompanyArgs>();
const where = convertToWhereInput(args.where);
const companies = await this.prismaService.client.company.findMany({
where,
});
assert(companies.length, '', NotFoundException);
const allowed = await relationAbilityChecker(
'Company',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
for (const company of companies) {
const allowed = ability.can(
AbilityAction.Delete,
subject('Company', company),
);
if (!allowed) {
return false;
}
}
return true;
}
}
@Injectable()
export class DeleteCompanyAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CompanyArgs>();
const where = convertToWhereInput(args.where);
const companies = await this.prismaService.client.company.findMany({
where,
});
assert(companies.length, '', NotFoundException);
for (const company of companies) {
const allowed = ability.can(
AbilityAction.Delete,
subject('Company', company),
);
if (!allowed) {
return false;
}
}
return true;
}
}

View File

@ -1,102 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { FavoriteWhereInput } from 'src/core/@generated/favorite/favorite-where.input';
import { assert } from 'src/utils/assert';
class FavoriteArgs {
where?: FavoriteWhereInput;
}
@Injectable()
export class ManageFavoriteAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Favorite');
}
}
@Injectable()
export class ReadFavoriteAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Favorite');
}
}
@Injectable()
export class CreateFavoriteAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'Favorite',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'Favorite');
}
}
@Injectable()
export class UpdateFavoriteAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<FavoriteArgs>();
const favorite = await this.prismaService.client.favorite.findFirst({
where: args.where,
});
assert(favorite, '', NotFoundException);
const allowed = await relationAbilityChecker(
'Favorite',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, 'Favorite');
}
}
@Injectable()
export class DeleteFavoriteAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<FavoriteArgs>();
const favorite = await this.prismaService.client.favorite.findFirst({
where: args.where,
});
assert(favorite, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('Favorite', favorite));
}
}

View File

@ -1,102 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { PersonWhereInput } from 'src/core/@generated/person/person-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class PersonArgs {
where?: PersonWhereInput;
[key: string]: any;
}
@Injectable()
export class ManagePersonAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Person');
}
}
@Injectable()
export class ReadPersonAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Person');
}
}
@Injectable()
export class CreatePersonAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'Person',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'Person');
}
}
@Injectable()
export class UpdatePersonAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PersonArgs>();
const person = await this.prismaService.client.person.findFirst({
where: args.where,
});
assert(person, '', NotFoundException);
const allowed = await relationAbilityChecker(
'Person',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, subject('Person', person));
}
}
@Injectable()
export class DeletePersonAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PersonArgs>();
const person = await this.prismaService.client.person.findFirst({
where: args.where,
});
assert(person, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('Person', person));
}
}

View File

@ -1,110 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { PipelineProgressWhereInput } from 'src/core/@generated/pipeline-progress/pipeline-progress-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class PipelineProgressArgs {
where?: PipelineProgressWhereInput;
[key: string]: any;
}
@Injectable()
export class ManagePipelineProgressAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'PipelineProgress');
}
}
@Injectable()
export class ReadPipelineProgressAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'PipelineProgress');
}
}
@Injectable()
export class CreatePipelineProgressAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'PipelineProgress',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'PipelineProgress');
}
}
@Injectable()
export class UpdatePipelineProgressAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PipelineProgressArgs>();
const pipelineProgress =
await this.prismaService.client.pipelineProgress.findFirst({
where: args.where,
});
assert(pipelineProgress, '', NotFoundException);
const allowed = await relationAbilityChecker(
'PipelineProgress',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(
AbilityAction.Update,
subject('PipelineProgress', pipelineProgress),
);
}
}
@Injectable()
export class DeletePipelineProgressAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PipelineProgressArgs>();
const pipelineProgress =
await this.prismaService.client.pipelineProgress.findFirst({
where: args.where,
});
assert(pipelineProgress, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('PipelineProgress', pipelineProgress),
);
}
}

View File

@ -1,110 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { PipelineStageWhereInput } from 'src/core/@generated/pipeline-stage/pipeline-stage-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class PipelineStageArgs {
where?: PipelineStageWhereInput;
[key: string]: any;
}
@Injectable()
export class ManagePipelineStageAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'PipelineStage');
}
}
@Injectable()
export class ReadPipelineStageAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'PipelineStage');
}
}
@Injectable()
export class CreatePipelineStageAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'PipelineStage',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'PipelineStage');
}
}
@Injectable()
export class UpdatePipelineStageAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PipelineStageArgs>();
const pipelineStage =
await this.prismaService.client.pipelineStage.findFirst({
where: args.where,
});
assert(pipelineStage, '', NotFoundException);
const allowed = await relationAbilityChecker(
'PipelineStage',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(
AbilityAction.Update,
subject('PipelineStage', pipelineStage),
);
}
}
@Injectable()
export class DeletePipelineStageAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PipelineStageArgs>();
const pipelineStage =
await this.prismaService.client.pipelineStage.findFirst({
where: args.where,
});
assert(pipelineStage, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('PipelineStage', pipelineStage),
);
}
}

View File

@ -1,102 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { PipelineWhereInput } from 'src/core/@generated/pipeline/pipeline-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class PipelineArgs {
where?: PipelineWhereInput;
[key: string]: any;
}
@Injectable()
export class ManagePipelineAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Pipeline');
}
}
@Injectable()
export class ReadPipelineAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Pipeline');
}
}
@Injectable()
export class CreatePipelineAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'Pipeline',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'Pipeline');
}
}
@Injectable()
export class UpdatePipelineAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PipelineArgs>();
const pipeline = await this.prismaService.client.pipeline.findFirst({
where: args.where,
});
assert(pipeline, '', NotFoundException);
const allowed = await relationAbilityChecker(
'Pipeline',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, subject('Pipeline', pipeline));
}
}
@Injectable()
export class DeletePipelineAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<PipelineArgs>();
const pipeline = await this.prismaService.client.pipeline.findFirst({
where: args.where,
});
assert(pipeline, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('Pipeline', pipeline));
}
}

View File

@ -1,112 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { RefreshTokenWhereInput } from 'src/core/@generated/refresh-token/refresh-token-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class RefreshTokenArgs {
where?: RefreshTokenWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageRefreshTokenAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'RefreshToken');
}
}
@Injectable()
export class ReadRefreshTokenAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'RefreshToken');
}
}
@Injectable()
export class CreateRefreshTokenAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'RefreshToken',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'RefreshToken');
}
}
@Injectable()
export class UpdateRefreshTokenAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<RefreshTokenArgs>();
const refreshToken = await this.prismaService.client.refreshToken.findFirst(
{
where: args.where,
},
);
assert(refreshToken, '', NotFoundException);
const allowed = await relationAbilityChecker(
'RefreshToken',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(
AbilityAction.Update,
subject('RefreshToken', refreshToken),
);
}
}
@Injectable()
export class DeleteRefreshTokenAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<RefreshTokenArgs>();
const refreshToken = await this.prismaService.client.refreshToken.findFirst(
{
where: args.where,
},
);
assert(refreshToken, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('RefreshToken', refreshToken),
);
}
}

View File

@ -1,109 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { UserWhereInput } from 'src/core/@generated/user/user-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class UserArgs {
where?: UserWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageUserAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'User');
}
}
@Injectable()
export class ReadUserAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'User');
}
}
@Injectable()
export class CreateUserAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'User',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'User');
}
}
@Injectable()
export class UpdateUserAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<UserArgs>();
// TODO: Confirm if this is correct
const user = await this.prismaService.client.user.findFirst({
where: args.where,
});
assert(user, '', NotFoundException);
const allowed = await relationAbilityChecker(
'User',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, subject('User', user));
}
}
@Injectable()
export class DeleteUserAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<UserArgs>();
// obtain the auth user from the context
const reqUser = gqlContext.getContext().req.user;
// FIXME: When `args.where` is undefined(which it is in almost all the cases I've tested),
// this query will return the first user entry in the DB, which is most likely not the current user
const user = await this.prismaService.client.user.findFirst({
where: { ...args.where, id: reqUser.user.id },
});
assert(user, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('User', user));
}
}

View File

@ -1,57 +0,0 @@
import {
Injectable,
ExecutionContext,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { AppAbility } from 'src/ability/ability.factory';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { assert } from 'src/utils/assert';
@Injectable()
export class CreateWebHookAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'WebHook',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'WebHook');
}
}
@Injectable()
export class DeleteWebHookAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const hook = await this.prismaService.client.webHook.findFirst({
where: args.where,
});
assert(hook, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('WebHook', hook));
}
}
@Injectable()
export class ReadWebHookAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'WebHook');
}
}

View File

@ -1,110 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { WorkspaceMemberWhereInput } from 'src/core/@generated/workspace-member/workspace-member-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class WorkspaceMemberArgs {
where?: WorkspaceMemberWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageWorkspaceMemberAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'WorkspaceMember');
}
}
@Injectable()
export class ReadWorkspaceMemberAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'WorkspaceMember');
}
}
@Injectable()
export class CreateWorkspaceMemberAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'WorkspaceMember',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'WorkspaceMember');
}
}
@Injectable()
export class UpdateWorkspaceMemberAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<WorkspaceMemberArgs>();
const workspaceMember =
await this.prismaService.client.workspaceMember.findFirst({
where: args.where,
});
assert(workspaceMember, '', NotFoundException);
const allowed = await relationAbilityChecker(
'WorkspaceMember',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(
AbilityAction.Update,
subject('WorkspaceMember', workspaceMember),
);
}
}
@Injectable()
export class DeleteWorkspaceMemberAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<WorkspaceMemberArgs>();
const workspaceMember =
await this.prismaService.client.workspaceMember.findFirst({
where: args.where,
});
assert(workspaceMember, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('WorkspaceMember', workspaceMember),
);
}
}

View File

@ -1,100 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { WorkspaceWhereInput } from 'src/core/@generated/workspace/workspace-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class WorkspaceArgs {
where?: WorkspaceWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageWorkspaceAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Workspace');
}
}
@Injectable()
export class ReadWorkspaceAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Workspace');
}
}
@Injectable()
export class CreateWorkspaceAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'Workspace',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'Workspace');
}
}
@Injectable()
export class UpdateWorkspaceAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<WorkspaceArgs>();
const workspace = await this.prismaService.client.workspace.findFirst({
where: args.where,
});
assert(workspace, '', NotFoundException);
const allowed = await relationAbilityChecker(
'Workspace',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Update, 'Workspace');
}
}
@Injectable()
export class DeleteWorkspaceAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<WorkspaceArgs>();
const workspace = await this.prismaService.client.workspace.findFirst({
where: args.where,
});
assert(workspace, '', NotFoundException);
return ability.can(AbilityAction.Delete, 'Workspace');
}
}

View File

@ -1,12 +0,0 @@
import { ExecutionContext, Type } from '@nestjs/common';
import { AppAbility } from 'src/ability/ability.factory';
export interface IAbilityHandler {
handle(
ability: AppAbility,
executionContext: ExecutionContext,
): Promise<boolean> | boolean;
}
export type AbilityHandler = Type<IAbilityHandler>;

View File

@ -13,9 +13,7 @@ import { AppService } from './app.service';
import { CoreModule } from './core/core.module';
import { IntegrationsModule } from './integrations/integrations.module';
import { PrismaModule } from './database/prisma.module';
import { HealthModule } from './health/health.module';
import { AbilityModule } from './ability/ability.module';
import { WorkspaceModule } from './workspace/workspace.module';
import { EnvironmentService } from './integrations/environment/environment.service';
import {
@ -104,9 +102,7 @@ import { ExceptionFilter } from './filters/exception.filter';
resolvers: { JSON: GraphQLJSON },
plugins: [],
}),
PrismaModule,
HealthModule,
AbilityModule,
IntegrationsModule,
CoreModule,
WorkspaceModule,

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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,
});
}
}

View File

@ -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();
});
});

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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;
}

View File

@ -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';

View File

@ -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';

View File

@ -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;
}

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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 },
});
}
}

View File

@ -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,
}),
};
}
}

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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;
}
}

View File

@ -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();
});
});

View File

@ -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;
}
}
}

View File

@ -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 {}

View File

@ -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,
);
}
}

View File

@ -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);

View File

@ -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;
}

View 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;
}

View File

@ -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;
}

View File

@ -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';

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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 };
}
}

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

@ -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();
});
});

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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,
},
},
},
});
}
}

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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,
});
}
}

View File

@ -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();
});
});

View File

@ -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 } });
}
}

View File

@ -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
}
]

View File

@ -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 {}

View File

@ -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 {}

View File

@ -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,
});
}
}

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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,
},
},
},
});
}
}

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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];
}
}

View File

@ -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();
});
});

View File

@ -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

View File

@ -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 {}

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

@ -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();
});
});

View File

@ -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;
}
}

View File

@ -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();
});
});

View File

@ -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,
});
}
}

View File

@ -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"
}
]

View File

@ -1,6 +0,0 @@
{
"name": "Sales pipeline",
"icon": "💰",
"pipelineProgressableType": "Company"
}

View File

@ -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();
});
});

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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,
});
}
}

View File

@ -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();
});
});

View File

@ -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,
});
}
}

View File

@ -4,7 +4,7 @@ import {
} from '@ptc-org/nestjs-query-graphql';
import { v4 as uuidv4 } from 'uuid';
import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity';
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
export class BeforeCreateOneRefreshToken<T extends RefreshToken>
implements BeforeCreateOneHook<T, any>

View File

@ -9,38 +9,29 @@ import {
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import {
Authorize,
BeforeCreateOne,
IDField,
} from '@ptc-org/nestjs-query-graphql';
import { BeforeCreateOne, IDField } from '@ptc-org/nestjs-query-graphql';
import { UserV2 } from 'src/coreV2/user/user.entity';
import { User } from 'src/core/user/user.entity';
import { BeforeCreateOneRefreshToken } from './hooks/before-create-one-refresh-token.hook';
@Entity('refresh_tokens')
@ObjectType('refreshTokenV2')
@Entity({ name: 'refreshToken', schema: 'core' })
@ObjectType('RefreshToken')
@BeforeCreateOne(BeforeCreateOneRefreshToken)
@Authorize({
authorize: (context: any) => ({
userId: { eq: context?.req?.user?.user?.id },
}),
})
export class RefreshToken {
@IDField(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => UserV2, (user) => user.refreshTokens)
@ManyToOne(() => User, (user) => user.refreshTokens)
@JoinColumn({ name: 'userId' })
user: UserV2;
user: User;
@Column()
userId: string;
@Field()
@Column('time with time zone')
@Column('timestamp with time zone')
expiresAt: Date;
@Column('timestamp with time zone', { nullable: true })

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity';
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
import { RefreshTokenService } from './refresh-token.service';

View File

@ -1,5 +1,5 @@
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity';
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
export class RefreshTokenService extends TypeOrmQueryService<RefreshToken> {}

View 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;
}

Some files were not shown because too many files have changed in this diff Show More