Skip to content

Commit 83f175f

Browse files
authored
Merge pull request #93 from import-ai/refactor/perf
refactor(resources): optimize API
2 parents ac09653 + 17b217e commit 83f175f

File tree

12 files changed

+275
-73
lines changed

12 files changed

+275
-73
lines changed

src/app/app.module.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ import { LoggerMiddleware } from './logger.middleware';
8080
})
8181
export class AppModule implements NestModule {
8282
configure(consumer: MiddlewareConsumer) {
83-
consumer
84-
.apply(LoggerMiddleware)
85-
.forRoutes('*');
83+
consumer.apply(LoggerMiddleware).forRoutes('*');
8684
}
8785
}

src/app/logger.middleware.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@ export class LoggerMiddleware implements NestMiddleware {
1414
next();
1515
}
1616
}
17-

src/groups/entities/group-user.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export class GroupUser extends Base {
2828
@JoinColumn({ name: 'group_id' })
2929
group: Group;
3030

31+
@Column({ name: 'group_id', nullable: false })
32+
groupId: string;
33+
3134
@ManyToOne(() => User, { nullable: false })
3235
@JoinColumn({ name: 'user_id' })
3336
user: User;

src/namespaces/namespaces.service.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -290,20 +290,19 @@ export class NamespacesService {
290290
});
291291
}
292292

293-
async getRoot(namespace: string, spaceType: SpaceType, userId: string) {
293+
async getRoot(namespaceId: string, spaceType: SpaceType, userId: string) {
294294
let resource: Resource | null;
295295
if (spaceType === SpaceType.TEAMSPACE) {
296-
resource = await this.getTeamspaceRoot(namespace);
296+
resource = await this.getTeamspaceRoot(namespaceId);
297297
} else {
298-
resource = await this.getPrivateRoot(userId, namespace);
298+
resource = await this.getPrivateRoot(userId, namespaceId);
299299
}
300-
const children = await this.resourceService.query({
301-
namespaceId: namespace,
302-
spaceType,
303-
parentId: resource.id,
300+
const children = await this.resourceService.listChildren(
301+
namespaceId,
302+
resource.id,
304303
userId,
305-
});
306-
return { ...resource, parentId: '0', spaceType, children };
304+
);
305+
return { ...resource, parentId: '0', children };
307306
}
308307

309308
async userIsOwner(namespaceId: string, userId: string): Promise<boolean> {

src/permissions/entities/user-permission.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export class UserPermission extends Base {
3232
@JoinColumn({ name: 'resource_id' })
3333
resource?: Resource;
3434

35+
@Column({ name: 'resource_id', nullable: false })
36+
resourceId: string;
37+
3538
@ManyToOne(() => User, { nullable: false })
3639
@JoinColumn({ name: 'user_id' })
3740
user?: User;

src/permissions/permission-level.enum.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,26 @@ export function comparePermissionLevel(
2020
): number {
2121
return order.indexOf(a) - order.indexOf(b);
2222
}
23+
24+
export function maxPermission(
25+
a: PermissionLevel | null,
26+
b: PermissionLevel | null,
27+
): PermissionLevel | null {
28+
if (!a) {
29+
return b;
30+
}
31+
if (!b) {
32+
return a;
33+
}
34+
return comparePermissionLevel(a, b) > 0 ? a : b;
35+
}
36+
37+
export function maxPermissions(
38+
permissions: Array<PermissionLevel | null>,
39+
): PermissionLevel | null {
40+
let permission: PermissionLevel | null = null;
41+
for (const p of permissions) {
42+
permission = maxPermission(permission, p);
43+
}
44+
return permission;
45+
}

src/permissions/permissions.service.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { Injectable } from '@nestjs/common';
22
import { InjectRepository } from '@nestjs/typeorm';
3-
import { DataSource, EntityManager, IsNull, Repository } from 'typeorm';
3+
import { DataSource, EntityManager, In, IsNull, Repository } from 'typeorm';
44
import { PermissionDto } from './dto/permission.dto';
55
import { ListRespDto, UserPermissionDto } from './dto/list-resp.dto';
66
import { plainToInstance } from 'class-transformer';
77
import {
88
comparePermissionLevel,
9+
maxPermission,
10+
maxPermissions,
911
PermissionLevel,
1012
} from './permission-level.enum';
1113
import { UserPermission } from './entities/user-permission.entity';
@@ -14,6 +16,7 @@ import { Resource } from 'src/resources/resources.entity';
1416
import { UserService } from 'src/user/user.service';
1517
import { GroupUser } from 'src/groups/entities/group-user.entity';
1618
import { NamespaceMember } from 'src/namespaces/entities/namespace-member.entity';
19+
import { Record } from 'openai/core';
1720

1821
@Injectable()
1922
export class PermissionsService {
@@ -428,6 +431,102 @@ export class PermissionsService {
428431
return level;
429432
}
430433

434+
getGlobalPermissionFromParents(
435+
parentResources: Resource[],
436+
): PermissionLevel | null {
437+
for (const resource of parentResources) {
438+
if (resource.globalLevel) {
439+
return resource.globalLevel;
440+
}
441+
}
442+
return null;
443+
}
444+
445+
async getUserPermissionFromParents(
446+
namespaceId: string,
447+
parentResourceIds: string[],
448+
userId: string,
449+
): Promise<PermissionLevel | null> {
450+
const userPermissions = await this.userPermiRepo.find({
451+
where: {
452+
namespace: { id: namespaceId },
453+
user: { id: userId },
454+
resourceId: In(parentResourceIds),
455+
},
456+
});
457+
const userPermiMap: Map<string, UserPermission> = new Map(
458+
userPermissions.map((permi) => [permi.resourceId, permi]),
459+
);
460+
for (const resourceId of parentResourceIds) {
461+
const permission = userPermiMap.get(resourceId);
462+
if (permission) {
463+
return permission.level;
464+
}
465+
}
466+
return null;
467+
}
468+
469+
async getGroupPermissionFromParents(
470+
namespaceId: string,
471+
parentResourceIds: string[],
472+
groupIds: string[],
473+
): Promise<PermissionLevel | null> {
474+
const groupPermissions = await this.groupPermiRepo.find({
475+
where: {
476+
namespace: { id: namespaceId },
477+
resource: { id: In(parentResourceIds) },
478+
group: { id: In(groupIds) },
479+
},
480+
});
481+
const permiMap: Map<string, PermissionLevel | null> = new Map();
482+
for (const groupPermi of groupPermissions) {
483+
const resourceId = groupPermi.resource!.id;
484+
const curPermi = permiMap.get(resourceId) || null;
485+
permiMap.set(resourceId, maxPermission(curPermi, groupPermi.level));
486+
}
487+
for (const resourceId of parentResourceIds) {
488+
const permission = permiMap.get(resourceId);
489+
if (permission) {
490+
return permission;
491+
}
492+
}
493+
return null;
494+
}
495+
496+
async getCurrentPermissionFromParents(
497+
namespaceId: string,
498+
parentResources: Resource[],
499+
userId: string,
500+
): Promise<PermissionLevel> {
501+
const groups = await this.groupUserRepository.find({
502+
where: {
503+
namespace: { id: namespaceId },
504+
user: { id: userId },
505+
},
506+
});
507+
const groupIds = groups.map((group) => group.groupId);
508+
const parentResourceIds = parentResources.map((resource) => resource.id);
509+
510+
const globalPermission =
511+
this.getGlobalPermissionFromParents(parentResources);
512+
const userPermission = await this.getUserPermissionFromParents(
513+
namespaceId,
514+
parentResourceIds,
515+
userId,
516+
);
517+
const groupPermission = await this.getGroupPermissionFromParents(
518+
namespaceId,
519+
parentResourceIds,
520+
groupIds,
521+
);
522+
const curPermission = maxPermissions([
523+
globalPermission,
524+
userPermission,
525+
groupPermission,
526+
]);
527+
return curPermission || PermissionLevel.NO_ACCESS;
528+
}
529+
431530
async getParentId(
432531
namespaceId: string,
433532
resourceId: string,

src/resources/resources.controller.ts

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Delete,
99
ForbiddenException,
1010
Get,
11+
NotFoundException,
1112
Param,
1213
ParseIntPipe,
1314
Patch,
@@ -77,6 +78,19 @@ export class ResourcesController {
7778
});
7879
}
7980

81+
@Get(':resourceId/children')
82+
async listChildren(
83+
@Req() req,
84+
@Param('namespaceId') namespaceId: string,
85+
@Param('resourceId') resourceId: string,
86+
) {
87+
return this.resourcesService.listChildren(
88+
namespaceId,
89+
resourceId,
90+
req.user.id,
91+
);
92+
}
93+
8094
@Post(':resourceId/move/:targetId')
8195
async move(
8296
@Req() req,
@@ -107,48 +121,62 @@ export class ResourcesController {
107121
});
108122
}
109123

110-
@Get(':resourceId/path')
111-
async path(
112-
@Req() req,
113-
@Param('namespaceId') namespaceId: string,
114-
@Param('resourceId') resourceId: string,
115-
) {
116-
const resources: Array<Resource> = [];
117-
let currentResource = await this.resourcesService.get(resourceId);
118-
while (currentResource && currentResource.parentId) {
119-
resources.push(currentResource);
120-
currentResource = await this.resourcesService.get(
121-
currentResource.parentId,
122-
);
123-
}
124-
return await this.resourcesService.permissionFilter(
125-
namespaceId,
126-
req.user.id,
127-
resources,
128-
);
129-
}
124+
// @Get(':resourceId/path')
125+
// async path(
126+
// @Req() req,
127+
// @Param('namespaceId') namespaceId: string,
128+
// @Param('resourceId') resourceId: string,
129+
// ) {
130+
// const resources: Array<Resource> = [];
131+
// let currentResource = await this.resourcesService.get(resourceId);
132+
// while (currentResource && currentResource.parentId) {
133+
// resources.push(currentResource);
134+
// currentResource = await this.resourcesService.get(
135+
// currentResource.parentId,
136+
// );
137+
// }
138+
// return await this.resourcesService.permissionFilter(
139+
// namespaceId,
140+
// req.user.id,
141+
// resources,
142+
// );
143+
// }
130144

131145
@Get(':resourceId')
132146
async get(
133147
@Req() req,
134148
@Param('namespaceId') namespaceId: string,
135149
@Param('resourceId') resourceId: string,
136150
) {
137-
const hasPermission = await this.permissionsService.userHasPermission(
151+
const userId: string = req.user.id;
152+
153+
const resource = await this.resourcesService.get(resourceId);
154+
if (resource.namespaceId !== namespaceId) {
155+
throw new NotFoundException('Not found');
156+
}
157+
const parentResources = await this.resourcesService.getParentResources(
138158
namespaceId,
139-
resourceId,
140-
req.user.id,
159+
resource.parentId,
141160
);
142-
if (!hasPermission) {
161+
162+
const permission =
163+
await this.permissionsService.getCurrentPermissionFromParents(
164+
namespaceId,
165+
[resource, ...parentResources],
166+
userId,
167+
);
168+
if (permission === PermissionLevel.NO_ACCESS) {
143169
throw new ForbiddenException('Not authorized');
144170
}
145-
const currentLevel = await this.permissionsService.getCurrentLevel(
146-
namespaceId,
147-
resourceId,
148-
req.user.id,
149-
);
150-
const resource = await this.resourcesService.get(resourceId);
151-
return { ...resource, currentLevel };
171+
172+
const path = [resource, ...parentResources]
173+
.filter((r) => r.parentId) // remove root resource
174+
.map((r) => ({
175+
id: r.id,
176+
name: r.name,
177+
}))
178+
.reverse();
179+
return { ...resource, currentLevel: permission, path };
152180
}
153181

154182
@Patch(':resourceId')

src/resources/resources.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,7 @@ export class Resource extends Base {
6464
@ManyToOne(() => Namespace)
6565
@JoinColumn({ name: 'namespace_id' })
6666
namespace: Namespace;
67+
68+
@Column({ name: 'namespace_id' })
69+
namespaceId: string;
6770
}

0 commit comments

Comments
 (0)