mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-13 00:02:30 +10:00
feat: role authorizations - WIP
This commit is contained in:
@ -28,6 +28,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.456.0",
|
"@aws-sdk/client-s3": "^3.456.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.456.0",
|
"@aws-sdk/s3-request-presigner": "^3.456.0",
|
||||||
|
"@casl/ability": "^6.7.0",
|
||||||
"@fastify/multipart": "^8.1.0",
|
"@fastify/multipart": "^8.1.0",
|
||||||
"@fastify/static": "^6.12.0",
|
"@fastify/static": "^6.12.0",
|
||||||
"@nestjs/common": "^10.3.0",
|
"@nestjs/common": "^10.3.0",
|
||||||
|
|||||||
90
apps/server/src/core/casl/abilities/casl-ability.factory.ts
Normal file
90
apps/server/src/core/casl/abilities/casl-ability.factory.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AbilityBuilder,
|
||||||
|
createMongoAbility,
|
||||||
|
ExtractSubjectType,
|
||||||
|
InferSubjects,
|
||||||
|
MongoAbility,
|
||||||
|
} from '@casl/ability';
|
||||||
|
import { User } from '../../user/entities/user.entity';
|
||||||
|
import { Action } from '../ability.action';
|
||||||
|
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||||
|
import { WorkspaceUser } from '../../workspace/entities/workspace-user.entity';
|
||||||
|
import { WorkspaceInvitation } from '../../workspace/entities/workspace-invitation.entity';
|
||||||
|
import { Role } from '../../../helpers/types/permission';
|
||||||
|
import { Group } from '../../group/entities/group.entity';
|
||||||
|
import { GroupUser } from '../../group/entities/group-user.entity';
|
||||||
|
import { Attachment } from '../../attachment/entities/attachment.entity';
|
||||||
|
import { Space } from '../../space/entities/space.entity';
|
||||||
|
import { SpaceUser } from '../../space/entities/space-user.entity';
|
||||||
|
import { Page } from '../../page/entities/page.entity';
|
||||||
|
import { Comment } from '../../comment/entities/comment.entity';
|
||||||
|
|
||||||
|
export type Subjects =
|
||||||
|
| InferSubjects<
|
||||||
|
| typeof Workspace
|
||||||
|
| typeof WorkspaceUser
|
||||||
|
| typeof WorkspaceInvitation
|
||||||
|
| typeof Space
|
||||||
|
| typeof SpaceUser
|
||||||
|
| typeof Group
|
||||||
|
| typeof GroupUser
|
||||||
|
| typeof Attachment
|
||||||
|
| typeof Comment
|
||||||
|
| typeof Page
|
||||||
|
| typeof User
|
||||||
|
>
|
||||||
|
| 'all';
|
||||||
|
export type AppAbility = MongoAbility<[Action, Subjects]>;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class CaslAbilityFactory {
|
||||||
|
createForWorkspace(user: User, workspace: Workspace) {
|
||||||
|
const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
||||||
|
|
||||||
|
const userRole = workspace?.workspaceUser.role;
|
||||||
|
console.log(userRole);
|
||||||
|
|
||||||
|
if (userRole === Role.OWNER) {
|
||||||
|
// Workspace Users
|
||||||
|
can<any>([Action.Manage], Workspace);
|
||||||
|
can<any>([Action.Manage], WorkspaceUser);
|
||||||
|
can<any>([Action.Manage], WorkspaceInvitation);
|
||||||
|
|
||||||
|
// Groups
|
||||||
|
can<any>([Action.Manage], Group);
|
||||||
|
can<any>([Action.Manage], GroupUser);
|
||||||
|
|
||||||
|
// Attachments
|
||||||
|
can<any>([Action.Manage], Attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userRole === Role.MEMBER) {
|
||||||
|
can<any>([Action.Read], WorkspaceUser);
|
||||||
|
|
||||||
|
// Groups
|
||||||
|
can<any>([Action.Read], Group);
|
||||||
|
can<any>([Action.Read], GroupUser);
|
||||||
|
|
||||||
|
// Attachments
|
||||||
|
can<any>([Action.Read, Action.Create], Attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return build({
|
||||||
|
detectSubjectType: (item) =>
|
||||||
|
item.constructor as ExtractSubjectType<Subjects>,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createForUser(user: User) {
|
||||||
|
const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
||||||
|
|
||||||
|
can<any>([Action.Manage], User, { id: user.id });
|
||||||
|
can<any>([Action.Read], User);
|
||||||
|
|
||||||
|
return build({
|
||||||
|
detectSubjectType: (item) =>
|
||||||
|
item.constructor as ExtractSubjectType<Subjects>,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
7
apps/server/src/core/casl/ability.action.ts
Normal file
7
apps/server/src/core/casl/ability.action.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum Action {
|
||||||
|
Manage = 'manage',
|
||||||
|
Create = 'create',
|
||||||
|
Read = 'read',
|
||||||
|
Update = 'update',
|
||||||
|
Delete = 'delete',
|
||||||
|
}
|
||||||
9
apps/server/src/core/casl/casl.module.ts
Normal file
9
apps/server/src/core/casl/casl.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import CaslAbilityFactory from './abilities/casl-ability.factory';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [CaslAbilityFactory],
|
||||||
|
exports: [CaslAbilityFactory],
|
||||||
|
})
|
||||||
|
export class CaslModule {}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { PolicyHandler } from '../interfaces/policy-handler.interface';
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const CHECK_POLICIES_KEY = 'check_policy';
|
||||||
|
export const CheckPolicies = (...handlers: PolicyHandler[]) =>
|
||||||
|
SetMetadata(CHECK_POLICIES_KEY, handlers);
|
||||||
40
apps/server/src/core/casl/guards/policies.guard.ts
Normal file
40
apps/server/src/core/casl/guards/policies.guard.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import CaslAbilityFactory, {
|
||||||
|
AppAbility,
|
||||||
|
} from '../abilities/casl-ability.factory';
|
||||||
|
import { PolicyHandler } from '../interfaces/policy-handler.interface';
|
||||||
|
import { CHECK_POLICIES_KEY } from '../decorators/policies.decorator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PoliciesGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private reflector: Reflector,
|
||||||
|
private caslAbilityFactory: CaslAbilityFactory,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const policyHandlers =
|
||||||
|
this.reflector.get<PolicyHandler[]>(
|
||||||
|
CHECK_POLICIES_KEY,
|
||||||
|
context.getHandler(),
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const user = request['user'].user;
|
||||||
|
const workspace = request['user'].workspace;
|
||||||
|
|
||||||
|
const ability = this.caslAbilityFactory.createForWorkspace(user, workspace);
|
||||||
|
|
||||||
|
return policyHandlers.every((handler) =>
|
||||||
|
this.execPolicyHandler(handler, ability),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
|
||||||
|
if (typeof handler === 'function') {
|
||||||
|
return handler(ability);
|
||||||
|
}
|
||||||
|
return handler.handle(ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { AppAbility } from '../abilities/casl-ability.factory';
|
||||||
|
|
||||||
|
interface IPolicyHandler {
|
||||||
|
handle(ability: AppAbility): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
|
||||||
|
|
||||||
|
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
|
||||||
@ -10,6 +10,7 @@ import { CommentModule } from './comment/comment.module';
|
|||||||
import { SearchModule } from './search/search.module';
|
import { SearchModule } from './search/search.module';
|
||||||
import { SpaceModule } from './space/space.module';
|
import { SpaceModule } from './space/space.module';
|
||||||
import { GroupModule } from './group/group.module';
|
import { GroupModule } from './group/group.module';
|
||||||
|
import { CaslModule } from './casl/casl.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -25,6 +26,7 @@ import { GroupModule } from './group/group.module';
|
|||||||
SearchModule,
|
SearchModule,
|
||||||
SpaceModule,
|
SpaceModule,
|
||||||
GroupModule,
|
GroupModule,
|
||||||
|
CaslModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreModule {}
|
export class CoreModule {}
|
||||||
|
|||||||
@ -19,6 +19,12 @@ import { PaginationOptions } from '../../helpers/pagination/pagination-options';
|
|||||||
import { AddGroupUserDto } from './dto/add-group-user.dto';
|
import { AddGroupUserDto } from './dto/add-group-user.dto';
|
||||||
import { RemoveGroupUserDto } from './dto/remove-group-user.dto';
|
import { RemoveGroupUserDto } from './dto/remove-group-user.dto';
|
||||||
import { UpdateGroupDto } from './dto/update-group.dto';
|
import { UpdateGroupDto } from './dto/update-group.dto';
|
||||||
|
import { Action } from '../casl/ability.action';
|
||||||
|
import { Group } from './entities/group.entity';
|
||||||
|
import { GroupUser } from './entities/group-user.entity';
|
||||||
|
import { PoliciesGuard } from '../casl/guards/policies.guard';
|
||||||
|
import { CheckPolicies } from '../casl/decorators/policies.decorator';
|
||||||
|
import { AppAbility } from '../casl/abilities/casl-ability.factory';
|
||||||
|
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
@Controller('groups')
|
@Controller('groups')
|
||||||
@ -38,6 +44,8 @@ export class GroupController {
|
|||||||
return this.groupService.getGroupsInWorkspace(workspace.id, pagination);
|
return this.groupService.getGroupsInWorkspace(workspace.id, pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, Group))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('/details')
|
@Post('/details')
|
||||||
getGroup(
|
getGroup(
|
||||||
@ -48,6 +56,8 @@ export class GroupController {
|
|||||||
return this.groupService.getGroup(groupIdDto.groupId, workspace.id);
|
return this.groupService.getGroup(groupIdDto.groupId, workspace.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, Group))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('create')
|
@Post('create')
|
||||||
createGroup(
|
createGroup(
|
||||||
@ -58,6 +68,8 @@ export class GroupController {
|
|||||||
return this.groupService.createGroup(user, workspace.id, createGroupDto);
|
return this.groupService.createGroup(user, workspace.id, createGroupDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, Group))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('update')
|
@Post('update')
|
||||||
updateGroup(
|
updateGroup(
|
||||||
@ -68,6 +80,8 @@ export class GroupController {
|
|||||||
return this.groupService.updateGroup(workspace.id, updateGroupDto);
|
return this.groupService.updateGroup(workspace.id, updateGroupDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, GroupUser))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members')
|
@Post('members')
|
||||||
getGroupMembers(
|
getGroupMembers(
|
||||||
@ -82,6 +96,8 @@ export class GroupController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, GroupUser))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members/add')
|
@Post('members/add')
|
||||||
addGroupMember(
|
addGroupMember(
|
||||||
@ -96,6 +112,8 @@ export class GroupController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, GroupUser))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members/remove')
|
@Post('members/remove')
|
||||||
removeGroupMember(
|
removeGroupMember(
|
||||||
@ -109,6 +127,8 @@ export class GroupController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, Group))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('delete')
|
@Post('delete')
|
||||||
deleteGroup(
|
deleteGroup(
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
|
||||||
UseGuards,
|
UseGuards,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
@ -16,12 +15,12 @@ import { UpdateUserDto } from './dto/update-user.dto';
|
|||||||
import { AuthUser } from '../../decorators/auth-user.decorator';
|
import { AuthUser } from '../../decorators/auth-user.decorator';
|
||||||
|
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
@Controller('user')
|
@Controller('users')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Get('me')
|
@Post('me')
|
||||||
async getUser(@AuthUser() authUser: User) {
|
async getUser(@AuthUser() authUser: User) {
|
||||||
const user: User = await this.userService.findById(authUser.id);
|
const user: User = await this.userService.findById(authUser.id);
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ export class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Get('info')
|
@Post('info')
|
||||||
async getUserInfo(@AuthUser() user: User) {
|
async getUserInfo(@AuthUser() user: User) {
|
||||||
const data: { workspace: Workspace; user: User } =
|
const data: { workspace: Workspace; user: User } =
|
||||||
await this.userService.getUserInstance(user.id);
|
await this.userService.getUserInstance(user.id);
|
||||||
|
|||||||
@ -13,6 +13,12 @@ import { WorkspaceService } from '../workspace/services/workspace.service';
|
|||||||
import { DataSource, EntityManager } from 'typeorm';
|
import { DataSource, EntityManager } from 'typeorm';
|
||||||
import { transactionWrapper } from '../../helpers/db.helper';
|
import { transactionWrapper } from '../../helpers/db.helper';
|
||||||
import { CreateWorkspaceDto } from '../workspace/dto/create-workspace.dto';
|
import { CreateWorkspaceDto } from '../workspace/dto/create-workspace.dto';
|
||||||
|
import { Workspace } from '../workspace/entities/workspace.entity';
|
||||||
|
|
||||||
|
export type UserWithWorkspace = {
|
||||||
|
user: User;
|
||||||
|
workspace: Workspace;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@ -59,7 +65,7 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserInstance(userId: string) {
|
async getUserInstance(userId: string): Promise<UserWithWorkspace> {
|
||||||
const user: User = await this.findById(userId);
|
const user: User = await this.findById(userId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
@ -26,6 +26,12 @@ import {
|
|||||||
InviteUserDto,
|
InviteUserDto,
|
||||||
RevokeInviteDto,
|
RevokeInviteDto,
|
||||||
} from '../dto/invitation.dto';
|
} from '../dto/invitation.dto';
|
||||||
|
import { Action } from '../../casl/ability.action';
|
||||||
|
import { WorkspaceUser } from '../entities/workspace-user.entity';
|
||||||
|
import { WorkspaceInvitation } from '../entities/workspace-invitation.entity';
|
||||||
|
import { CheckPolicies } from '../../casl/decorators/policies.decorator';
|
||||||
|
import { AppAbility } from '../../casl/abilities/casl-ability.factory';
|
||||||
|
import { PoliciesGuard } from '../../casl/guards/policies.guard';
|
||||||
|
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
@Controller('workspaces')
|
@Controller('workspaces')
|
||||||
@ -57,6 +63,8 @@ export class WorkspaceController {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, Workspace))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('update')
|
@Post('update')
|
||||||
async updateWorkspace(
|
async updateWorkspace(
|
||||||
@ -66,12 +74,18 @@ export class WorkspaceController {
|
|||||||
return this.workspaceService.update(workspace.id, updateWorkspaceDto);
|
return this.workspaceService.update(workspace.id, updateWorkspaceDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, Workspace))
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('delete')
|
@Post('delete')
|
||||||
async deleteWorkspace(@Body() deleteWorkspaceDto: DeleteWorkspaceDto) {
|
async deleteWorkspace(@Body() deleteWorkspaceDto: DeleteWorkspaceDto) {
|
||||||
return this.workspaceService.delete(deleteWorkspaceDto);
|
return this.workspaceService.delete(deleteWorkspaceDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) =>
|
||||||
|
ability.can(Action.Read, WorkspaceUser),
|
||||||
|
)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members')
|
@Post('members')
|
||||||
async getWorkspaceMembers(
|
async getWorkspaceMembers(
|
||||||
@ -85,6 +99,10 @@ export class WorkspaceController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) =>
|
||||||
|
ability.can(Action.Manage, WorkspaceUser),
|
||||||
|
)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members/add')
|
@Post('members/add')
|
||||||
async addWorkspaceMember(
|
async addWorkspaceMember(
|
||||||
@ -98,6 +116,10 @@ export class WorkspaceController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) =>
|
||||||
|
ability.can(Action.Manage, WorkspaceUser),
|
||||||
|
)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members/remove')
|
@Post('members/remove')
|
||||||
async removeWorkspaceMember(
|
async removeWorkspaceMember(
|
||||||
@ -110,6 +132,10 @@ export class WorkspaceController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) =>
|
||||||
|
ability.can(Action.Manage, WorkspaceUser),
|
||||||
|
)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('members/role')
|
@Post('members/role')
|
||||||
async updateWorkspaceMemberRole(
|
async updateWorkspaceMemberRole(
|
||||||
@ -124,6 +150,10 @@ export class WorkspaceController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies((ability: AppAbility) =>
|
||||||
|
ability.can(Action.Manage, WorkspaceInvitation),
|
||||||
|
)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('invite')
|
@Post('invite')
|
||||||
async inviteUser(
|
async inviteUser(
|
||||||
|
|||||||
@ -86,4 +86,6 @@ export class Workspace {
|
|||||||
|
|
||||||
@OneToMany(() => Group, (group) => group.workspace)
|
@OneToMany(() => Group, (group) => group.workspace)
|
||||||
groups: [];
|
groups: [];
|
||||||
|
|
||||||
|
workspaceUser?: WorkspaceUser;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
|
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
|
||||||
import { WorkspaceRepository } from '../repositories/workspace.repository';
|
import { WorkspaceRepository } from '../repositories/workspace.repository';
|
||||||
import { WorkspaceUserRepository } from '../repositories/workspace-user.repository';
|
import { WorkspaceUserRepository } from '../repositories/workspace-user.repository';
|
||||||
@ -15,12 +11,10 @@ import { plainToInstance } from 'class-transformer';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
|
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
|
||||||
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
|
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
|
||||||
import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto';
|
|
||||||
import { SpaceService } from '../../space/space.service';
|
import { SpaceService } from '../../space/space.service';
|
||||||
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
||||||
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
|
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
|
||||||
import { PaginatedResult } from '../../../helpers/pagination/paginated-result';
|
import { PaginatedResult } from '../../../helpers/pagination/paginated-result';
|
||||||
import { User } from '../../user/entities/user.entity';
|
|
||||||
import { DataSource, EntityManager } from 'typeorm';
|
import { DataSource, EntityManager } from 'typeorm';
|
||||||
import { transactionWrapper } from '../../../helpers/db.helper';
|
import { transactionWrapper } from '../../../helpers/db.helper';
|
||||||
import { CreateSpaceDto } from '../../space/dto/create-space.dto';
|
import { CreateSpaceDto } from '../../space/dto/create-space.dto';
|
||||||
@ -187,8 +181,8 @@ export class WorkspaceService {
|
|||||||
|
|
||||||
async getUserCurrentWorkspace(userId: string): Promise<Workspace> {
|
async getUserCurrentWorkspace(userId: string): Promise<Workspace> {
|
||||||
const userWorkspace = await this.workspaceUserRepository.findOne({
|
const userWorkspace = await this.workspaceUserRepository.findOne({
|
||||||
where: { userId: userId },
|
|
||||||
relations: ['workspace'],
|
relations: ['workspace'],
|
||||||
|
where: { userId: userId },
|
||||||
order: {
|
order: {
|
||||||
createdAt: 'ASC',
|
createdAt: 'ASC',
|
||||||
},
|
},
|
||||||
@ -198,7 +192,8 @@ export class WorkspaceService {
|
|||||||
throw new NotFoundException('No workspace found for this user');
|
throw new NotFoundException('No workspace found for this user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return userWorkspace.workspace;
|
const { workspace, ...workspaceUser } = userWorkspace;
|
||||||
|
return { ...workspace, workspaceUser } as Workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserWorkspaces(
|
async getUserWorkspaces(
|
||||||
|
|||||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@ -256,6 +256,9 @@ importers:
|
|||||||
'@aws-sdk/s3-request-presigner':
|
'@aws-sdk/s3-request-presigner':
|
||||||
specifier: ^3.456.0
|
specifier: ^3.456.0
|
||||||
version: 3.485.0
|
version: 3.485.0
|
||||||
|
'@casl/ability':
|
||||||
|
specifier: ^6.7.0
|
||||||
|
version: 6.7.0
|
||||||
'@fastify/multipart':
|
'@fastify/multipart':
|
||||||
specifier: ^8.1.0
|
specifier: ^8.1.0
|
||||||
version: 8.1.0
|
version: 8.1.0
|
||||||
@ -2379,6 +2382,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@casl/ability@6.7.0:
|
||||||
|
resolution: {integrity: sha512-NC51ha1nnfCMy88Gdk7cTBipv6n3QNo1yZA68EklsUIzWVDhTs9jJ5y70c3LpT6sN1GcUnGBP/cF7M2I4TkQ3w==}
|
||||||
|
dependencies:
|
||||||
|
'@ucast/mongo2js': 1.3.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@colors/colors@1.5.0:
|
/@colors/colors@1.5.0:
|
||||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||||
engines: {node: '>=0.1.90'}
|
engines: {node: '>=0.1.90'}
|
||||||
@ -5394,6 +5403,30 @@ packages:
|
|||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@ucast/core@1.10.2:
|
||||||
|
resolution: {integrity: sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@ucast/js@3.0.4:
|
||||||
|
resolution: {integrity: sha512-TgG1aIaCMdcaEyckOZKQozn1hazE0w90SVdlpIJ/er8xVumE11gYAtSbw/LBeUnA4fFnFWTcw3t6reqseeH/4Q==}
|
||||||
|
dependencies:
|
||||||
|
'@ucast/core': 1.10.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@ucast/mongo2js@1.3.4:
|
||||||
|
resolution: {integrity: sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==}
|
||||||
|
dependencies:
|
||||||
|
'@ucast/core': 1.10.2
|
||||||
|
'@ucast/js': 3.0.4
|
||||||
|
'@ucast/mongo': 2.4.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@ucast/mongo@2.4.3:
|
||||||
|
resolution: {integrity: sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==}
|
||||||
|
dependencies:
|
||||||
|
'@ucast/core': 1.10.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@ungap/structured-clone@1.2.0:
|
/@ungap/structured-clone@1.2.0:
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
Reference in New Issue
Block a user