mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat(server): make permission a standalone module (#7880)
This commit is contained in:
@@ -12,6 +12,7 @@ import { AuthModule } from './core/auth';
|
|||||||
import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config';
|
import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config';
|
||||||
import { DocModule } from './core/doc';
|
import { DocModule } from './core/doc';
|
||||||
import { FeatureModule } from './core/features';
|
import { FeatureModule } from './core/features';
|
||||||
|
import { PermissionModule } from './core/permission';
|
||||||
import { QuotaModule } from './core/quota';
|
import { QuotaModule } from './core/quota';
|
||||||
import { SelfhostModule } from './core/selfhost';
|
import { SelfhostModule } from './core/selfhost';
|
||||||
import { StorageModule } from './core/storage';
|
import { StorageModule } from './core/storage';
|
||||||
@@ -41,7 +42,6 @@ import { ENABLED_PLUGINS } from './plugins/registry';
|
|||||||
|
|
||||||
export const FunctionalityModules = [
|
export const FunctionalityModules = [
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(),
|
||||||
ScheduleModule.forRoot(),
|
|
||||||
EventModule,
|
EventModule,
|
||||||
CacheModule,
|
CacheModule,
|
||||||
MutexModule,
|
MutexModule,
|
||||||
@@ -147,12 +147,12 @@ export function buildAppModule() {
|
|||||||
const factor = new AppModuleBuilder(AFFiNE);
|
const factor = new AppModuleBuilder(AFFiNE);
|
||||||
|
|
||||||
factor
|
factor
|
||||||
// common fundamental modules
|
// basic
|
||||||
.use(...FunctionalityModules)
|
.use(...FunctionalityModules)
|
||||||
.useIf(config => config.flavor.sync, WebSocketModule)
|
.useIf(config => config.flavor.sync, WebSocketModule)
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
.use(UserModule, AuthModule)
|
.use(UserModule, AuthModule, PermissionModule)
|
||||||
|
|
||||||
// business modules
|
// business modules
|
||||||
.use(DocModule)
|
.use(DocModule)
|
||||||
@@ -163,9 +163,10 @@ export function buildAppModule() {
|
|||||||
// graphql server only
|
// graphql server only
|
||||||
.useIf(
|
.useIf(
|
||||||
config => config.flavor.graphql,
|
config => config.flavor.graphql,
|
||||||
ServerConfigModule,
|
ScheduleModule.forRoot(),
|
||||||
GqlModule,
|
GqlModule,
|
||||||
StorageModule,
|
StorageModule,
|
||||||
|
ServerConfigModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
FeatureModule,
|
FeatureModule,
|
||||||
QuotaModule
|
QuotaModule
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import {
|
|||||||
DocNotFound,
|
DocNotFound,
|
||||||
metrics,
|
metrics,
|
||||||
OnEvent,
|
OnEvent,
|
||||||
WorkspaceNotFound,
|
|
||||||
} from '../../fundamentals';
|
} from '../../fundamentals';
|
||||||
|
import { PermissionService } from '../permission';
|
||||||
import { QuotaService } from '../quota';
|
import { QuotaService } from '../quota';
|
||||||
import { Permission } from '../workspaces/types';
|
|
||||||
import { isEmptyBuffer } from './manager';
|
import { isEmptyBuffer } from './manager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -23,7 +22,8 @@ export class DocHistoryManager {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly config: Config,
|
private readonly config: Config,
|
||||||
private readonly db: PrismaClient,
|
private readonly db: PrismaClient,
|
||||||
private readonly quota: QuotaService
|
private readonly quota: QuotaService,
|
||||||
|
private readonly permission: PermissionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@OnEvent('workspace.deleted')
|
@OnEvent('workspace.deleted')
|
||||||
@@ -235,21 +235,8 @@ export class DocHistoryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getExpiredDateFromNow(workspaceId: string) {
|
async getExpiredDateFromNow(workspaceId: string) {
|
||||||
const permission = await this.db.workspaceUserPermission.findFirst({
|
const owner = await this.permission.getWorkspaceOwner(workspaceId);
|
||||||
select: {
|
const quota = await this.quota.getUserQuota(owner.id);
|
||||||
userId: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
type: Permission.Owner,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!permission) {
|
|
||||||
throw new WorkspaceNotFound({ workspaceId });
|
|
||||||
}
|
|
||||||
|
|
||||||
const quota = await this.quota.getUserQuota(permission.userId);
|
|
||||||
return quota.feature.historyPeriodFromNow;
|
return quota.feature.historyPeriodFromNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import './config';
|
|||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PermissionModule } from '../permission';
|
||||||
import { QuotaModule } from '../quota';
|
import { QuotaModule } from '../quota';
|
||||||
import { DocHistoryManager } from './history';
|
import { DocHistoryManager } from './history';
|
||||||
import { DocManager } from './manager';
|
import { DocManager } from './manager';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [QuotaModule],
|
imports: [QuotaModule, PermissionModule],
|
||||||
providers: [DocManager, DocHistoryManager],
|
providers: [DocManager, DocHistoryManager],
|
||||||
exports: [DocManager, DocHistoryManager],
|
exports: [DocManager, DocHistoryManager],
|
||||||
})
|
})
|
||||||
|
|||||||
12
packages/backend/server/src/core/permission/index.ts
Normal file
12
packages/backend/server/src/core/permission/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PermissionService } from './service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [PermissionService],
|
||||||
|
exports: [PermissionService],
|
||||||
|
})
|
||||||
|
export class PermissionModule {}
|
||||||
|
|
||||||
|
export { PermissionService } from './service';
|
||||||
|
export { Permission, PublicPageMode } from './types';
|
||||||
@@ -2,13 +2,12 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import type { Prisma } from '@prisma/client';
|
import type { Prisma } from '@prisma/client';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
import { DocAccessDenied, WorkspaceAccessDenied } from '../../fundamentals';
|
import {
|
||||||
import { Permission } from './types';
|
DocAccessDenied,
|
||||||
|
WorkspaceAccessDenied,
|
||||||
export enum PublicPageMode {
|
WorkspaceOwnerNotFound,
|
||||||
Page,
|
} from '../../fundamentals';
|
||||||
Edgeless,
|
import { Permission, PublicPageMode } from './types';
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PermissionService {
|
export class PermissionService {
|
||||||
@@ -59,7 +58,7 @@ export class PermissionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceOwner(workspaceId: string) {
|
async getWorkspaceOwner(workspaceId: string) {
|
||||||
return this.prisma.workspaceUserPermission.findFirstOrThrow({
|
const owner = await this.prisma.workspaceUserPermission.findFirst({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Owner,
|
type: Permission.Owner,
|
||||||
@@ -68,6 +67,12 @@ export class PermissionService {
|
|||||||
user: true,
|
user: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!owner) {
|
||||||
|
throw new WorkspaceOwnerNotFound({ workspaceId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return owner.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryGetWorkspaceOwner(workspaceId: string) {
|
async tryGetWorkspaceOwner(workspaceId: string) {
|
||||||
@@ -195,6 +200,17 @@ export class PermissionService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async allowUrlPreview(ws: string) {
|
||||||
|
const count = await this.prisma.workspace.count({
|
||||||
|
where: {
|
||||||
|
id: ws,
|
||||||
|
public: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
async grant(
|
async grant(
|
||||||
ws: string,
|
ws: string,
|
||||||
user: string,
|
user: string,
|
||||||
11
packages/backend/server/src/core/permission/types.ts
Normal file
11
packages/backend/server/src/core/permission/types.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export enum Permission {
|
||||||
|
Read = 0,
|
||||||
|
Write = 1,
|
||||||
|
Admin = 10,
|
||||||
|
Owner = 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PublicPageMode {
|
||||||
|
Page,
|
||||||
|
Edgeless,
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { FeatureModule } from '../features';
|
import { FeatureModule } from '../features';
|
||||||
|
import { PermissionModule } from '../permission';
|
||||||
import { StorageModule } from '../storage';
|
import { StorageModule } from '../storage';
|
||||||
import { PermissionService } from '../workspaces/permission';
|
|
||||||
import { QuotaManagementResolver } from './resolver';
|
import { QuotaManagementResolver } from './resolver';
|
||||||
import { QuotaService } from './service';
|
import { QuotaService } from './service';
|
||||||
import { QuotaManagementService } from './storage';
|
import { QuotaManagementService } from './storage';
|
||||||
@@ -14,13 +14,8 @@ import { QuotaManagementService } from './storage';
|
|||||||
* - quota statistics
|
* - quota statistics
|
||||||
*/
|
*/
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FeatureModule, StorageModule],
|
imports: [FeatureModule, StorageModule, PermissionModule],
|
||||||
providers: [
|
providers: [QuotaService, QuotaManagementResolver, QuotaManagementService],
|
||||||
PermissionService,
|
|
||||||
QuotaService,
|
|
||||||
QuotaManagementResolver,
|
|
||||||
QuotaManagementService,
|
|
||||||
],
|
|
||||||
exports: [QuotaService, QuotaManagementService],
|
exports: [QuotaService, QuotaManagementService],
|
||||||
})
|
})
|
||||||
export class QuotaModule {}
|
export class QuotaModule {}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
import { WorkspaceOwnerNotFound } from '../../fundamentals';
|
|
||||||
import { FeatureService, FeatureType } from '../features';
|
import { FeatureService, FeatureType } from '../features';
|
||||||
|
import { PermissionService } from '../permission';
|
||||||
import { WorkspaceBlobStorage } from '../storage';
|
import { WorkspaceBlobStorage } from '../storage';
|
||||||
import { PermissionService } from '../workspaces/permission';
|
|
||||||
import { OneGB } from './constant';
|
import { OneGB } from './constant';
|
||||||
import { QuotaService } from './service';
|
import { QuotaService } from './service';
|
||||||
import { formatSize, QuotaQueryType } from './types';
|
import { formatSize, QuotaQueryType } from './types';
|
||||||
@@ -113,9 +112,7 @@ export class QuotaManagementService {
|
|||||||
// get workspace's owner quota and total size of used
|
// get workspace's owner quota and total size of used
|
||||||
// quota was apply to owner's account
|
// quota was apply to owner's account
|
||||||
async getWorkspaceUsage(workspaceId: string): Promise<QuotaBusinessType> {
|
async getWorkspaceUsage(workspaceId: string): Promise<QuotaBusinessType> {
|
||||||
const { user: owner } =
|
const owner = await this.permissions.getWorkspaceOwner(workspaceId);
|
||||||
await this.permissions.getWorkspaceOwner(workspaceId);
|
|
||||||
if (!owner) throw new WorkspaceOwnerNotFound({ workspaceId });
|
|
||||||
const {
|
const {
|
||||||
feature: {
|
feature: {
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ import {
|
|||||||
} from '../../../fundamentals';
|
} from '../../../fundamentals';
|
||||||
import { Auth, CurrentUser } from '../../auth';
|
import { Auth, CurrentUser } from '../../auth';
|
||||||
import { DocManager } from '../../doc';
|
import { DocManager } from '../../doc';
|
||||||
|
import { Permission, PermissionService } from '../../permission';
|
||||||
import { DocID } from '../../utils/doc';
|
import { DocID } from '../../utils/doc';
|
||||||
import { PermissionService } from '../../workspaces/permission';
|
|
||||||
import { Permission } from '../../workspaces/types';
|
|
||||||
|
|
||||||
const SubscribeMessage = (event: string) =>
|
const SubscribeMessage = (event: string) =>
|
||||||
applyDecorators(
|
applyDecorators(
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { DocModule } from '../../doc';
|
import { DocModule } from '../../doc';
|
||||||
import { PermissionService } from '../../workspaces/permission';
|
import { PermissionModule } from '../../permission';
|
||||||
import { EventsGateway } from './events.gateway';
|
import { EventsGateway } from './events.gateway';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DocModule],
|
imports: [DocModule, PermissionModule],
|
||||||
providers: [EventsGateway, PermissionService],
|
providers: [EventsGateway],
|
||||||
})
|
})
|
||||||
export class EventsModule {}
|
export class EventsModule {}
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import {
|
|||||||
} from '../../fundamentals';
|
} from '../../fundamentals';
|
||||||
import { CurrentUser, Public } from '../auth';
|
import { CurrentUser, Public } from '../auth';
|
||||||
import { DocHistoryManager, DocManager } from '../doc';
|
import { DocHistoryManager, DocManager } from '../doc';
|
||||||
|
import { Permission, PermissionService, PublicPageMode } from '../permission';
|
||||||
import { WorkspaceBlobStorage } from '../storage';
|
import { WorkspaceBlobStorage } from '../storage';
|
||||||
import { DocID } from '../utils/doc';
|
import { DocID } from '../utils/doc';
|
||||||
import { PermissionService, PublicPageMode } from './permission';
|
|
||||||
import { Permission } from './types';
|
|
||||||
|
|
||||||
@Controller('/api/workspaces')
|
@Controller('/api/workspaces')
|
||||||
export class WorkspacesController {
|
export class WorkspacesController {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { DocModule } from '../doc';
|
import { DocModule } from '../doc';
|
||||||
import { FeatureModule } from '../features';
|
import { FeatureModule } from '../features';
|
||||||
|
import { PermissionModule } from '../permission';
|
||||||
import { QuotaModule } from '../quota';
|
import { QuotaModule } from '../quota';
|
||||||
import { StorageModule } from '../storage';
|
import { StorageModule } from '../storage';
|
||||||
import { UserModule } from '../user';
|
import { UserModule } from '../user';
|
||||||
import { WorkspacesController } from './controller';
|
import { WorkspacesController } from './controller';
|
||||||
import { WorkspaceManagementResolver } from './management';
|
import { WorkspaceManagementResolver } from './management';
|
||||||
import { PermissionService } from './permission';
|
|
||||||
import {
|
import {
|
||||||
DocHistoryResolver,
|
DocHistoryResolver,
|
||||||
PagePermissionResolver,
|
PagePermissionResolver,
|
||||||
@@ -16,17 +16,22 @@ import {
|
|||||||
} from './resolvers';
|
} from './resolvers';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DocModule, FeatureModule, QuotaModule, StorageModule, UserModule],
|
imports: [
|
||||||
|
DocModule,
|
||||||
|
FeatureModule,
|
||||||
|
QuotaModule,
|
||||||
|
StorageModule,
|
||||||
|
UserModule,
|
||||||
|
PermissionModule,
|
||||||
|
],
|
||||||
controllers: [WorkspacesController],
|
controllers: [WorkspacesController],
|
||||||
providers: [
|
providers: [
|
||||||
WorkspaceResolver,
|
WorkspaceResolver,
|
||||||
WorkspaceManagementResolver,
|
WorkspaceManagementResolver,
|
||||||
PermissionService,
|
|
||||||
PagePermissionResolver,
|
PagePermissionResolver,
|
||||||
DocHistoryResolver,
|
DocHistoryResolver,
|
||||||
WorkspaceBlobResolver,
|
WorkspaceBlobResolver,
|
||||||
],
|
],
|
||||||
exports: [PermissionService],
|
|
||||||
})
|
})
|
||||||
export class WorkspaceModule {}
|
export class WorkspaceModule {}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { ActionForbidden } from '../../fundamentals';
|
|||||||
import { CurrentUser } from '../auth';
|
import { CurrentUser } from '../auth';
|
||||||
import { Admin } from '../common';
|
import { Admin } from '../common';
|
||||||
import { FeatureManagementService, FeatureType } from '../features';
|
import { FeatureManagementService, FeatureType } from '../features';
|
||||||
import { PermissionService } from './permission';
|
import { PermissionService } from '../permission';
|
||||||
import { WorkspaceType } from './types';
|
import { WorkspaceType } from './types';
|
||||||
|
|
||||||
@Resolver(() => WorkspaceType)
|
@Resolver(() => WorkspaceType)
|
||||||
@@ -61,7 +61,7 @@ export class WorkspaceManagementResolver {
|
|||||||
|
|
||||||
const owner = await this.permission.getWorkspaceOwner(workspaceId);
|
const owner = await this.permission.getWorkspaceOwner(workspaceId);
|
||||||
const availableFeatures = await this.availableFeatures(user);
|
const availableFeatures = await this.availableFeatures(user);
|
||||||
if (owner.user.id !== user.id || !availableFeatures.includes(feature)) {
|
if (owner.id !== user.id || !availableFeatures.includes(feature)) {
|
||||||
throw new ActionForbidden();
|
throw new ActionForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ import {
|
|||||||
PreventCache,
|
PreventCache,
|
||||||
} from '../../../fundamentals';
|
} from '../../../fundamentals';
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
|
import { Permission, PermissionService } from '../../permission';
|
||||||
import { QuotaManagementService } from '../../quota';
|
import { QuotaManagementService } from '../../quota';
|
||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
import { PermissionService } from '../permission';
|
import { WorkspaceBlobSizes, WorkspaceType } from '../types';
|
||||||
import { Permission, WorkspaceBlobSizes, WorkspaceType } from '../types';
|
|
||||||
|
|
||||||
@UseGuards(CloudThrottlerGuard)
|
@UseGuards(CloudThrottlerGuard)
|
||||||
@Resolver(() => WorkspaceType)
|
@Resolver(() => WorkspaceType)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import type { SnapshotHistory } from '@prisma/client';
|
|||||||
|
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
import { DocHistoryManager } from '../../doc';
|
import { DocHistoryManager } from '../../doc';
|
||||||
|
import { Permission, PermissionService } from '../../permission';
|
||||||
import { DocID } from '../../utils/doc';
|
import { DocID } from '../../utils/doc';
|
||||||
import { PermissionService } from '../permission';
|
import { WorkspaceType } from '../types';
|
||||||
import { Permission, WorkspaceType } from '../types';
|
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
class DocHistoryType implements Partial<SnapshotHistory> {
|
class DocHistoryType implements Partial<SnapshotHistory> {
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ import {
|
|||||||
PageIsNotPublic,
|
PageIsNotPublic,
|
||||||
} from '../../../fundamentals';
|
} from '../../../fundamentals';
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
|
import {
|
||||||
|
Permission,
|
||||||
|
PermissionService,
|
||||||
|
PublicPageMode,
|
||||||
|
} from '../../permission';
|
||||||
import { DocID } from '../../utils/doc';
|
import { DocID } from '../../utils/doc';
|
||||||
import { PermissionService, PublicPageMode } from '../permission';
|
import { WorkspaceType } from '../types';
|
||||||
import { Permission, WorkspaceType } from '../types';
|
|
||||||
|
|
||||||
registerEnumType(PublicPageMode, {
|
registerEnumType(PublicPageMode, {
|
||||||
name: 'PublicPageMode',
|
name: 'PublicPageMode',
|
||||||
|
|||||||
@@ -26,17 +26,15 @@ import {
|
|||||||
UserNotFound,
|
UserNotFound,
|
||||||
WorkspaceAccessDenied,
|
WorkspaceAccessDenied,
|
||||||
WorkspaceNotFound,
|
WorkspaceNotFound,
|
||||||
WorkspaceOwnerNotFound,
|
|
||||||
} from '../../../fundamentals';
|
} from '../../../fundamentals';
|
||||||
import { CurrentUser, Public } from '../../auth';
|
import { CurrentUser, Public } from '../../auth';
|
||||||
|
import { Permission, PermissionService } from '../../permission';
|
||||||
import { QuotaManagementService, QuotaQueryType } from '../../quota';
|
import { QuotaManagementService, QuotaQueryType } from '../../quota';
|
||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
import { UserService, UserType } from '../../user';
|
import { UserService, UserType } from '../../user';
|
||||||
import { PermissionService } from '../permission';
|
|
||||||
import {
|
import {
|
||||||
InvitationType,
|
InvitationType,
|
||||||
InviteUserType,
|
InviteUserType,
|
||||||
Permission,
|
|
||||||
UpdateWorkspaceInput,
|
UpdateWorkspaceInput,
|
||||||
WorkspaceType,
|
WorkspaceType,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
@@ -101,9 +99,7 @@ export class WorkspaceResolver {
|
|||||||
complexity: 2,
|
complexity: 2,
|
||||||
})
|
})
|
||||||
async owner(@Parent() workspace: WorkspaceType) {
|
async owner(@Parent() workspace: WorkspaceType) {
|
||||||
const data = await this.permissions.getWorkspaceOwner(workspace.id);
|
return this.permissions.getWorkspaceOwner(workspace.id);
|
||||||
|
|
||||||
return data.user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => [InviteUserType], {
|
@ResolveField(() => [InviteUserType], {
|
||||||
@@ -449,7 +445,7 @@ export class WorkspaceResolver {
|
|||||||
avatar: avatar || defaultWorkspaceAvatar,
|
avatar: avatar || defaultWorkspaceAvatar,
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
},
|
},
|
||||||
user: owner.user,
|
user: owner,
|
||||||
invitee: invitee.user,
|
invitee: invitee.user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -507,12 +503,8 @@ export class WorkspaceResolver {
|
|||||||
|
|
||||||
const owner = await this.permissions.getWorkspaceOwner(workspaceId);
|
const owner = await this.permissions.getWorkspaceOwner(workspaceId);
|
||||||
|
|
||||||
if (!owner.user) {
|
|
||||||
throw new WorkspaceOwnerNotFound({ workspaceId: workspaceId });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendLeaveMail) {
|
if (sendLeaveMail) {
|
||||||
await this.mailer.sendLeaveWorkspaceEmail(owner.user.email, {
|
await this.mailer.sendLeaveWorkspaceEmail(owner.email, {
|
||||||
workspaceName,
|
workspaceName,
|
||||||
inviteeName: user.name,
|
inviteeName: user.name,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,15 +11,9 @@ import {
|
|||||||
import type { Workspace } from '@prisma/client';
|
import type { Workspace } from '@prisma/client';
|
||||||
import { SafeIntResolver } from 'graphql-scalars';
|
import { SafeIntResolver } from 'graphql-scalars';
|
||||||
|
|
||||||
|
import { Permission } from '../permission';
|
||||||
import { UserType } from '../user/types';
|
import { UserType } from '../user/types';
|
||||||
|
|
||||||
export enum Permission {
|
|
||||||
Read = 0,
|
|
||||||
Write = 1,
|
|
||||||
Admin = 10,
|
|
||||||
Owner = 99,
|
|
||||||
}
|
|
||||||
|
|
||||||
registerEnumType(Permission, {
|
registerEnumType(Permission, {
|
||||||
name: 'Permission',
|
name: 'Permission',
|
||||||
description: 'User permission in workspace',
|
description: 'User permission in workspace',
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
type: 'too_many_requests',
|
type: 'too_many_requests',
|
||||||
message: 'Too many requests.',
|
message: 'Too many requests.',
|
||||||
},
|
},
|
||||||
|
not_found: {
|
||||||
|
type: 'resource_not_found',
|
||||||
|
message: 'Resource not found.',
|
||||||
|
},
|
||||||
|
|
||||||
// User Errors
|
// User Errors
|
||||||
user_not_found: {
|
user_not_found: {
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export class TooManyRequest extends UserFriendlyError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class NotFound extends UserFriendlyError {
|
||||||
|
constructor(message?: string) {
|
||||||
|
super('resource_not_found', 'not_found', message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class UserNotFound extends UserFriendlyError {
|
export class UserNotFound extends UserFriendlyError {
|
||||||
constructor(message?: string) {
|
constructor(message?: string) {
|
||||||
super('resource_not_found', 'user_not_found', message);
|
super('resource_not_found', 'user_not_found', message);
|
||||||
@@ -508,6 +514,7 @@ export class CannotDeleteOwnAccount extends UserFriendlyError {
|
|||||||
export enum ErrorNames {
|
export enum ErrorNames {
|
||||||
INTERNAL_SERVER_ERROR,
|
INTERNAL_SERVER_ERROR,
|
||||||
TOO_MANY_REQUEST,
|
TOO_MANY_REQUEST,
|
||||||
|
NOT_FOUND,
|
||||||
USER_NOT_FOUND,
|
USER_NOT_FOUND,
|
||||||
USER_AVATAR_NOT_FOUND,
|
USER_AVATAR_NOT_FOUND,
|
||||||
EMAIL_ALREADY_USED,
|
EMAIL_ALREADY_USED,
|
||||||
|
|||||||
@@ -40,5 +40,3 @@ export class ErrorModule implements OnModuleInit {
|
|||||||
|
|
||||||
export { UserFriendlyError } from './def';
|
export { UserFriendlyError } from './def';
|
||||||
export * from './errors.gen';
|
export * from './errors.gen';
|
||||||
export * from './payment-required';
|
|
||||||
export * from './too-many-requests';
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
|
||||||
|
|
||||||
export class PaymentRequiredException extends HttpException {
|
|
||||||
constructor(desc?: string, code: string = 'Payment Required') {
|
|
||||||
super(
|
|
||||||
HttpException.createBody(desc ?? code, code, HttpStatus.PAYMENT_REQUIRED),
|
|
||||||
HttpStatus.PAYMENT_REQUIRED
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
|
||||||
|
|
||||||
export class TooManyRequestsException extends HttpException {
|
|
||||||
constructor(desc?: string, code: string = 'Too Many Requests') {
|
|
||||||
super(
|
|
||||||
HttpException.createBody(
|
|
||||||
desc ?? code,
|
|
||||||
code,
|
|
||||||
HttpStatus.TOO_MANY_REQUESTS
|
|
||||||
),
|
|
||||||
HttpStatus.TOO_MANY_REQUESTS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import { ArgumentsHost, Catch, Logger } from '@nestjs/common';
|
import {
|
||||||
|
ArgumentsHost,
|
||||||
|
Catch,
|
||||||
|
Logger,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { BaseExceptionFilter } from '@nestjs/core';
|
import { BaseExceptionFilter } from '@nestjs/core';
|
||||||
import { GqlContextType } from '@nestjs/graphql';
|
import { GqlContextType } from '@nestjs/graphql';
|
||||||
import { ThrottlerException } from '@nestjs/throttler';
|
import { ThrottlerException } from '@nestjs/throttler';
|
||||||
@@ -9,6 +14,7 @@ import { Socket } from 'socket.io';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
|
NotFound,
|
||||||
TooManyRequest,
|
TooManyRequest,
|
||||||
UserFriendlyError,
|
UserFriendlyError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
@@ -19,6 +25,8 @@ export function mapAnyError(error: any): UserFriendlyError {
|
|||||||
return error;
|
return error;
|
||||||
} else if (error instanceof ThrottlerException) {
|
} else if (error instanceof ThrottlerException) {
|
||||||
return new TooManyRequest();
|
return new TooManyRequest();
|
||||||
|
} else if (error instanceof NotFoundException) {
|
||||||
|
return new NotFound();
|
||||||
} else {
|
} else {
|
||||||
const e = new InternalServerError();
|
const e = new InternalServerError();
|
||||||
e.cause = error;
|
e.cause = error;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import './config';
|
|||||||
|
|
||||||
import { ServerFeature } from '../../core/config';
|
import { ServerFeature } from '../../core/config';
|
||||||
import { FeatureModule } from '../../core/features';
|
import { FeatureModule } from '../../core/features';
|
||||||
|
import { PermissionModule } from '../../core/permission';
|
||||||
import { QuotaModule } from '../../core/quota';
|
import { QuotaModule } from '../../core/quota';
|
||||||
import { PermissionService } from '../../core/workspaces/permission';
|
|
||||||
import { Plugin } from '../registry';
|
import { Plugin } from '../registry';
|
||||||
import { CopilotController } from './controller';
|
import { CopilotController } from './controller';
|
||||||
import { ChatMessageCache } from './message';
|
import { ChatMessageCache } from './message';
|
||||||
@@ -29,9 +29,8 @@ registerCopilotProvider(OpenAIProvider);
|
|||||||
|
|
||||||
@Plugin({
|
@Plugin({
|
||||||
name: 'copilot',
|
name: 'copilot',
|
||||||
imports: [FeatureModule, QuotaModule],
|
imports: [FeatureModule, QuotaModule, PermissionModule],
|
||||||
providers: [
|
providers: [
|
||||||
PermissionService,
|
|
||||||
ChatSessionService,
|
ChatSessionService,
|
||||||
CopilotResolver,
|
CopilotResolver,
|
||||||
ChatMessageCache,
|
ChatMessageCache,
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
|||||||
|
|
||||||
import { CurrentUser } from '../../core/auth';
|
import { CurrentUser } from '../../core/auth';
|
||||||
import { Admin } from '../../core/common';
|
import { Admin } from '../../core/common';
|
||||||
|
import { PermissionService } from '../../core/permission';
|
||||||
import { UserType } from '../../core/user';
|
import { UserType } from '../../core/user';
|
||||||
import { PermissionService } from '../../core/workspaces/permission';
|
|
||||||
import {
|
import {
|
||||||
CopilotFailedToCreateMessage,
|
CopilotFailedToCreateMessage,
|
||||||
FileUpload,
|
FileUpload,
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ import { OAuthService } from './service';
|
|||||||
],
|
],
|
||||||
controllers: [OAuthController],
|
controllers: [OAuthController],
|
||||||
contributesTo: ServerFeature.OAuth,
|
contributesTo: ServerFeature.OAuth,
|
||||||
if: config => !!config.plugins.oauth,
|
if: config => config.flavor.graphql && !!config.plugins.oauth,
|
||||||
})
|
})
|
||||||
export class OAuthModule {}
|
export class OAuthModule {}
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ enum ErrorNames {
|
|||||||
MAILER_SERVICE_IS_NOT_CONFIGURED
|
MAILER_SERVICE_IS_NOT_CONFIGURED
|
||||||
MEMBER_QUOTA_EXCEEDED
|
MEMBER_QUOTA_EXCEEDED
|
||||||
MISSING_OAUTH_QUERY_PARAMETER
|
MISSING_OAUTH_QUERY_PARAMETER
|
||||||
|
NOT_FOUND
|
||||||
NOT_IN_WORKSPACE
|
NOT_IN_WORKSPACE
|
||||||
NO_COPILOT_PROVIDER_AVAILABLE
|
NO_COPILOT_PROVIDER_AVAILABLE
|
||||||
OAUTH_ACCOUNT_ALREADY_CONNECTED
|
OAUTH_ACCOUNT_ALREADY_CONNECTED
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import {
|
|||||||
FeatureService,
|
FeatureService,
|
||||||
FeatureType,
|
FeatureType,
|
||||||
} from '../src/core/features';
|
} from '../src/core/features';
|
||||||
|
import { Permission } from '../src/core/permission';
|
||||||
import { UserType } from '../src/core/user/types';
|
import { UserType } from '../src/core/user/types';
|
||||||
import { WorkspaceResolver } from '../src/core/workspaces/resolvers';
|
import { WorkspaceResolver } from '../src/core/workspaces/resolvers';
|
||||||
import { Permission } from '../src/core/workspaces/types';
|
|
||||||
import { Config, ConfigModule } from '../src/fundamentals/config';
|
import { Config, ConfigModule } from '../src/fundamentals/config';
|
||||||
import { createTestingApp } from './utils';
|
import { createTestingApp } from './utils';
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import test from 'ava';
|
|||||||
import * as Sinon from 'sinon';
|
import * as Sinon from 'sinon';
|
||||||
|
|
||||||
import { DocHistoryManager } from '../src/core/doc';
|
import { DocHistoryManager } from '../src/core/doc';
|
||||||
|
import { PermissionModule } from '../src/core/permission';
|
||||||
import { QuotaModule } from '../src/core/quota';
|
import { QuotaModule } from '../src/core/quota';
|
||||||
import { StorageModule } from '../src/core/storage';
|
import { StorageModule } from '../src/core/storage';
|
||||||
import type { EventPayload } from '../src/fundamentals/event';
|
import type { EventPayload } from '../src/fundamentals/event';
|
||||||
@@ -17,7 +18,7 @@ let db: PrismaClient;
|
|||||||
// cleanup database before each test
|
// cleanup database before each test
|
||||||
test.beforeEach(async () => {
|
test.beforeEach(async () => {
|
||||||
m = await createTestingModule({
|
m = await createTestingModule({
|
||||||
imports: [StorageModule, QuotaModule],
|
imports: [StorageModule, QuotaModule, PermissionModule],
|
||||||
providers: [DocHistoryManager],
|
providers: [DocHistoryManager],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user