diff --git a/apps/nestjs-backend/src/features/access-token/access-token.service.ts b/apps/nestjs-backend/src/features/access-token/access-token.service.ts index 5884bbae5..cfe16e8fb 100644 --- a/apps/nestjs-backend/src/features/access-token/access-token.service.ts +++ b/apps/nestjs-backend/src/features/access-token/access-token.service.ts @@ -1,5 +1,5 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; -import type { AllActions } from '@teable/core'; +import type { Action } from '@teable/core'; import { generateAccessTokenId, getRandomString } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import type { @@ -34,7 +34,7 @@ export class AccessTokenService { return { ...accessTokenEntity, description: description || undefined, - scopes: JSON.parse(scopes) as AllActions[], + scopes: JSON.parse(scopes) as Action[], spaceIds: spaceIds ? (JSON.parse(spaceIds) as string[]) : undefined, baseIds: baseIds ? (JSON.parse(baseIds) as string[]) : undefined, createdTime: createdTime?.toISOString(), diff --git a/apps/nestjs-backend/src/features/auth/decorators/permissions.decorator.ts b/apps/nestjs-backend/src/features/auth/decorators/permissions.decorator.ts index 0871e1b75..8ade9c478 100644 --- a/apps/nestjs-backend/src/features/auth/decorators/permissions.decorator.ts +++ b/apps/nestjs-backend/src/features/auth/decorators/permissions.decorator.ts @@ -1,8 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import type { PermissionAction } from '@teable/core'; +import type { Action } from '@teable/core'; export const PERMISSIONS_KEY = 'permissions'; // eslint-disable-next-line @typescript-eslint/naming-convention -export const Permissions = (...permissions: PermissionAction[]) => - SetMetadata(PERMISSIONS_KEY, permissions); +export const Permissions = (...permissions: Action[]) => SetMetadata(PERMISSIONS_KEY, permissions); diff --git a/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts b/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts index e308fdffe..120fa7f4e 100644 --- a/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts +++ b/apps/nestjs-backend/src/features/auth/guard/permission.guard.ts @@ -1,7 +1,7 @@ import type { ExecutionContext } from '@nestjs/common'; import { ForbiddenException, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { type PermissionAction } from '@teable/core'; +import { type Action } from '@teable/core'; import { ClsService } from 'nestjs-cls'; import type { IClsStore } from '../../../types/cls'; import { IS_DISABLED_PERMISSION } from '../decorators/disabled-permission.decorator'; @@ -48,10 +48,7 @@ export class PermissionGuard { return true; } - protected async resourcePermission( - resourceId: string | undefined, - permissions: PermissionAction[] - ) { + protected async resourcePermission(resourceId: string | undefined, permissions: Action[]) { if (!resourceId) { throw new ForbiddenException('permission check ID does not exist'); } @@ -66,10 +63,10 @@ export class PermissionGuard { } protected async permissionCheck(context: ExecutionContext) { - const permissions = this.reflector.getAllAndOverride( - PERMISSIONS_KEY, - [context.getHandler(), context.getClass()] - ); + const permissions = this.reflector.getAllAndOverride(PERMISSIONS_KEY, [ + context.getHandler(), + context.getClass(), + ]); const accessTokenId = this.cls.get('accessTokenId'); if (accessTokenId && !permissions?.length) { diff --git a/apps/nestjs-backend/src/features/auth/permission.service.spec.ts b/apps/nestjs-backend/src/features/auth/permission.service.spec.ts index d28cf2a33..e368093c9 100644 --- a/apps/nestjs-backend/src/features/auth/permission.service.spec.ts +++ b/apps/nestjs-backend/src/features/auth/permission.service.spec.ts @@ -3,8 +3,8 @@ import { ForbiddenException, NotFoundException } from '@nestjs/common'; import type { TestingModule } from '@nestjs/testing'; import { Test } from '@nestjs/testing'; -import type { AllActions } from '@teable/core'; -import { RoleType, SpaceRole, getPermissions } from '@teable/core'; +import type { Action } from '@teable/core'; +import { Role, getPermissions } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import { noop } from 'lodash'; import { ClsService } from 'nestjs-cls'; @@ -218,7 +218,7 @@ describe('PermissionService', () => { it('should return scopes when resourceId is a valid spaceId and allowed', async () => { const resourceId = 'spcxxxxxxx'; const accessTokenId = 'validAccessTokenId'; - const scopes: AllActions[] = ['table|create', 'table|update']; + const scopes: Action[] = ['table|create', 'table|update']; const spaceIds = ['spcxxxxxxx']; vi.spyOn(service, 'getAccessToken').mockResolvedValueOnce({ @@ -288,7 +288,7 @@ describe('PermissionService', () => { it('should return permissions for a user', async () => { const resourceId = 'bsexxxxxx'; vi.spyOn(service, 'getPermissionsByResourceId').mockResolvedValue( - getPermissions(RoleType.Space, SpaceRole.Editor) + getPermissions(Role.Editor) ); const result = await service.getPermissions(resourceId); expect(result.includes('view|create')).toEqual(true); @@ -298,7 +298,7 @@ describe('PermissionService', () => { it('should return permissions for access token', async () => { const resourceId = 'bsexxxxxx'; vi.spyOn(service, 'getPermissionsByResourceId').mockResolvedValue( - getPermissions(RoleType.Space, SpaceRole.Editor) + getPermissions(Role.Editor) ); vi.spyOn(service, 'getPermissionsByAccessToken').mockResolvedValue([ 'view|create', @@ -313,7 +313,7 @@ describe('PermissionService', () => { describe('validPermissions', () => { it('should return true if user has all required permissions', async () => { - const permissions = getPermissions(RoleType.Space, SpaceRole.Creator); + const permissions = getPermissions(Role.Creator); vi.spyOn(service, 'getPermissions').mockResolvedValue(permissions); const resourceId = 'bsexxxxxx'; const result = await service.validPermissions(resourceId, ['base|create']); @@ -321,9 +321,7 @@ describe('PermissionService', () => { }); it('should throw an error if user does not have all required permissions', async () => { - vi.spyOn(service, 'getPermissions').mockResolvedValue( - getPermissions(RoleType.Space, SpaceRole.Editor) - ); + vi.spyOn(service, 'getPermissions').mockResolvedValue(getPermissions(Role.Editor)); const resourceId = 'bsexxxxxx'; await expect(service.validPermissions(resourceId, ['space|create'])).rejects.toThrowError( new ForbiddenException(`not allowed to operate space|create on ${resourceId}`) diff --git a/apps/nestjs-backend/src/features/auth/permission.service.ts b/apps/nestjs-backend/src/features/auth/permission.service.ts index 295cf139d..b61667db0 100644 --- a/apps/nestjs-backend/src/features/auth/permission.service.ts +++ b/apps/nestjs-backend/src/features/auth/permission.service.ts @@ -1,10 +1,9 @@ import { ForbiddenException, NotFoundException, Injectable } from '@nestjs/common'; -import type { BaseRole, PermissionAction, SpaceRole } from '@teable/core'; +import type { IBaseRole, Action, IRole } from '@teable/core'; import { IdPrefix, getPermissions } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import { intersection } from 'lodash'; import { ClsService } from 'nestjs-cls'; -import { RoleType } from '../../../../../packages/core/src/auth/types'; import type { IClsStore } from '../../types/cls'; @Injectable() @@ -29,7 +28,7 @@ export class PermissionService { if (!collaborator) { throw new ForbiddenException(`can't find collaborator`); } - return collaborator.roleName as SpaceRole; + return collaborator.roleName as IRole; } async getRoleByBaseId(baseId: string) { @@ -47,7 +46,7 @@ export class PermissionService { if (!collaborator) { return null; } - return collaborator.roleName as BaseRole; + return collaborator.roleName as IBaseRole; } async getOAuthAccessBy(userId: string) { @@ -83,7 +82,7 @@ export class PermissionService { where: { id: accessTokenId }, select: { scopes: true, spaceIds: true, baseIds: true, clientId: true, userId: true }, }); - const scopes = JSON.parse(stringifyScopes) as PermissionAction[]; + const scopes = JSON.parse(stringifyScopes) as Action[]; if (clientId) { const { spaceIds: spaceIdsByOAuth, baseIds: baseIdsByOAuth } = await this.getOAuthAccessBy(userId); @@ -186,13 +185,13 @@ export class PermissionService { private async getPermissionBySpaceId(spaceId: string) { const role = await this.getRoleBySpaceId(spaceId); - return getPermissions(RoleType.Space, role); + return getPermissions(role); } private async getPermissionByBaseId(baseId: string) { const role = await this.getRoleByBaseId(baseId); if (role) { - return getPermissions(RoleType.Base, role); + return getPermissions(role); } return this.getPermissionBySpaceId((await this.getUpperIdByBaseId(baseId)).spaceId); } @@ -227,11 +226,7 @@ export class PermissionService { return userPermissions; } - async validPermissions( - resourceId: string, - permissions: PermissionAction[], - accessTokenId?: string - ) { + async validPermissions(resourceId: string, permissions: Action[], accessTokenId?: string) { const ownPermissions = await this.getPermissions(resourceId, accessTokenId); if (permissions.every((permission) => ownPermissions.includes(permission))) { return ownPermissions; diff --git a/apps/nestjs-backend/src/features/collaborator/collaborator.service.spec.ts b/apps/nestjs-backend/src/features/collaborator/collaborator.service.spec.ts index 0f2681beb..0c793252f 100644 --- a/apps/nestjs-backend/src/features/collaborator/collaborator.service.spec.ts +++ b/apps/nestjs-backend/src/features/collaborator/collaborator.service.spec.ts @@ -1,6 +1,6 @@ import type { TestingModule } from '@nestjs/testing'; import { Test } from '@nestjs/testing'; -import { RoleType, SpaceRole, getPermissions } from '@teable/core'; +import { Role, getPermissions } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import { ClsService } from 'nestjs-cls'; import { mockDeep } from 'vitest-mock-extended'; @@ -41,21 +41,17 @@ describe('CollaboratorService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => { - await collaboratorService.createSpaceCollaborator( - mockUser.id, - mockSpace.id, - SpaceRole.Owner - ); + await collaboratorService.createSpaceCollaborator(mockUser.id, mockSpace.id, Role.Owner); } ); expect(prismaService.collaborator.create).toBeCalledWith({ data: { spaceId: mockSpace.id, - roleName: SpaceRole.Owner, + roleName: Role.Owner, userId: mockUser.id, createdBy: mockUser.id, }, @@ -66,7 +62,7 @@ describe('CollaboratorService', () => { prismaService.collaborator.count.mockResolvedValue(1); await expect( - collaboratorService.createSpaceCollaborator(mockUser.id, mockSpace.id, SpaceRole.Owner) + collaboratorService.createSpaceCollaborator(mockUser.id, mockSpace.id, Role.Owner) ).rejects.toThrow('has already existed in space'); }); }); diff --git a/apps/nestjs-backend/src/features/collaborator/collaborator.service.ts b/apps/nestjs-backend/src/features/collaborator/collaborator.service.ts index 10b333d18..0daea46b1 100644 --- a/apps/nestjs-backend/src/features/collaborator/collaborator.service.ts +++ b/apps/nestjs-backend/src/features/collaborator/collaborator.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import type { SpaceRole } from '@teable/core'; +import type { IRole } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import { UploadType, @@ -30,7 +30,7 @@ export class CollaboratorService { @InjectModel('CUSTOM_KNEX') private readonly knex: Knex ) {} - async createSpaceCollaborator(userId: string, spaceId: string, role: SpaceRole) { + async createSpaceCollaborator(userId: string, spaceId: string, role: IRole) { const currentUserId = this.cls.get('user.id'); const exist = await this.prismaService .txClient() @@ -181,17 +181,17 @@ export class CollaboratorService { spaceId: true, }, }); - const roleMap: Record = {}; + const roleMap: Record = {}; const baseIds = new Set(); const spaceIds = new Set(); collaborators.forEach(({ baseId, spaceId, roleName }) => { if (baseId) { baseIds.add(baseId); - roleMap[baseId] = roleName as SpaceRole; + roleMap[baseId] = roleName as IRole; } if (spaceId) { spaceIds.add(spaceId); - roleMap[spaceId] = roleName as SpaceRole; + roleMap[spaceId] = roleName as IRole; } }); return { diff --git a/apps/nestjs-backend/src/features/invitation/invitation.service.spec.ts b/apps/nestjs-backend/src/features/invitation/invitation.service.spec.ts index 71828b57d..8edb2cb18 100644 --- a/apps/nestjs-backend/src/features/invitation/invitation.service.spec.ts +++ b/apps/nestjs-backend/src/features/invitation/invitation.service.spec.ts @@ -2,7 +2,7 @@ import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common'; import type { TestingModule } from '@nestjs/testing'; import { Test } from '@nestjs/testing'; -import { getPermissions, RoleType, SpaceRole } from '@teable/core'; +import { getPermissions, Role } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import { ClsService } from 'nestjs-cls'; import { vi } from 'vitest'; @@ -63,11 +63,11 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => { await invitationService.generateInvitationBySpace('link', mockSpace.id, { - role: SpaceRole.Owner, + role: Role.Owner, }); } ); @@ -77,7 +77,7 @@ describe('InvitationService', () => { id: expect.anything(), invitationCode: expect.anything(), spaceId: mockSpace.id, - role: SpaceRole.Owner, + role: Role.Owner, type: 'link', expiredTime: null, createdBy: mockUser.id, @@ -92,7 +92,7 @@ describe('InvitationService', () => { await expect( invitationService.emailInvitationBySpace(mockSpace.id, { emails: ['notfound@example.com'], - role: SpaceRole.Owner, + role: Role.Owner, }) ).rejects.toThrow('Space not found'); }); @@ -110,19 +110,19 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await invitationService.emailInvitationBySpace(mockSpace.id, { emails: [mockInvitedUser.email], - role: SpaceRole.Owner, + role: Role.Owner, }) ); expect(collaboratorService.createSpaceCollaborator).toHaveBeenCalledWith( mockInvitedUser.id, mockSpace.id, - SpaceRole.Owner + Role.Owner ); expect(prismaService.invitationRecord.create).toHaveBeenCalledWith({ data: { @@ -145,7 +145,7 @@ describe('InvitationService', () => { await expect( invitationService.emailInvitationBySpace(mockSpace.id, { emails: [mockInvitedUser.email], - role: SpaceRole.Owner, + role: Role.Owner, }) ).rejects.toThrow('tx error'); }); @@ -167,7 +167,7 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await expect(() => @@ -182,7 +182,7 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await expect(() => @@ -200,7 +200,7 @@ describe('InvitationService', () => { baseId: null, deletedTime: null, createdTime: new Date('2022-01-02'), - role: SpaceRole.Owner, + role: Role.Owner, createdBy: mockUser.id, lastModifiedBy: null, lastModifiedTime: null, @@ -209,7 +209,7 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await expect(() => @@ -227,7 +227,7 @@ describe('InvitationService', () => { baseId: null, deletedTime: null, createdTime: new Date(), - role: SpaceRole.Owner, + role: Role.Owner, createdBy: mockUser.id, lastModifiedBy: null, lastModifiedTime: null, @@ -237,7 +237,7 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await invitationService.acceptInvitationLink(acceptInvitationLinkRo) ); @@ -250,7 +250,7 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await invitationService.acceptInvitationLink(acceptInvitationLinkRo) ); @@ -266,7 +266,7 @@ describe('InvitationService', () => { baseId: null, deletedTime: null, createdTime: new Date('2022-01-02'), - role: SpaceRole.Owner, + role: Role.Owner, createdBy: 'createdBy', lastModifiedBy: null, lastModifiedTime: null, @@ -278,7 +278,7 @@ describe('InvitationService', () => { { user: mockUser, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await invitationService.acceptInvitationLink(acceptInvitationLinkRo) ); diff --git a/apps/nestjs-backend/src/features/invitation/invitation.service.ts b/apps/nestjs-backend/src/features/invitation/invitation.service.ts index 74e7a0a73..14df0fcc4 100644 --- a/apps/nestjs-backend/src/features/invitation/invitation.service.ts +++ b/apps/nestjs-backend/src/features/invitation/invitation.service.ts @@ -5,7 +5,7 @@ import { NotFoundException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import type { SpaceRole } from '@teable/core'; +import type { IRole } from '@teable/core'; import { generateInvitationId } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import type { @@ -136,7 +136,7 @@ export class InvitationService { await this.generateInvitationBySpace('link', spaceId, data); return { invitationId: id, - role: role as SpaceRole, + role: role as IRole, createdBy, createdTime: createdTime.toISOString(), inviteUrl: this.generateInviteUrl(id, invitationCode), @@ -184,7 +184,7 @@ export class InvitationService { }); return { invitationId: id, - role: role as SpaceRole, + role: role as IRole, }; } @@ -195,7 +195,7 @@ export class InvitationService { }); return data.map(({ id, role, createdBy, createdTime, invitationCode }) => ({ invitationId: id, - role: role as SpaceRole, + role: role as IRole, createdBy, createdTime: createdTime.toISOString(), invitationCode, diff --git a/apps/nestjs-backend/src/features/selection/selection.service.spec.ts b/apps/nestjs-backend/src/features/selection/selection.service.spec.ts index a01f57ad2..8f9e8f1e4 100644 --- a/apps/nestjs-backend/src/features/selection/selection.service.spec.ts +++ b/apps/nestjs-backend/src/features/selection/selection.service.spec.ts @@ -17,8 +17,7 @@ import { defaultUserFieldOptions, getPermissions, nullsToUndefined, - SpaceRole, - RoleType, + Role, DateFormattingPreset, TimeFormatting, } from '@teable/core'; @@ -148,7 +147,7 @@ describe('selectionService', () => { { user: {} as any, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => selectionService['calculateExpansion'](tableSize, cell, tableDataSize) ); @@ -159,7 +158,7 @@ describe('selectionService', () => { { user: {} as any, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Editor), + permissions: getPermissions(Role.Editor), }, async () => selectionService['calculateExpansion'](tableSize, cell, tableDataSize) ); @@ -533,7 +532,7 @@ describe('selectionService', () => { { user: {} as any, tx: {}, - permissions: getPermissions(RoleType.Space, SpaceRole.Owner), + permissions: getPermissions(Role.Owner), }, async () => await selectionService.paste(tableId, { viewId, ...pasteRo }) ); diff --git a/apps/nestjs-backend/src/features/space/space.service.ts b/apps/nestjs-backend/src/features/space/space.service.ts index 4eb6c1cfc..6d8a839c9 100644 --- a/apps/nestjs-backend/src/features/space/space.service.ts +++ b/apps/nestjs-backend/src/features/space/space.service.ts @@ -1,5 +1,6 @@ import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; -import { SpaceRole, generateSpaceId, getUniqName } from '@teable/core'; +import type { IRole } from '@teable/core'; +import { Role, generateSpaceId, getUniqName } from '@teable/core'; import type { Prisma } from '@teable/db-main-prisma'; import { PrismaService } from '@teable/db-main-prisma'; import type { ICreateSpaceRo, IUpdateSpaceRo } from '@teable/openapi'; @@ -28,7 +29,7 @@ export class SpaceService { await this.collaboratorService.createSpaceCollaborator( spaceCreateInput.createdBy, result.id, - SpaceRole.Owner + Role.Owner ); return result; }); @@ -65,7 +66,7 @@ export class SpaceService { } return { ...space, - role: collaborator.roleName as SpaceRole, + role: collaborator.roleName as IRole, }; } @@ -92,7 +93,7 @@ export class SpaceService { const roleMap = keyBy(collaboratorSpaceList, 'spaceId'); return spaceList.map((space) => ({ ...space, - role: roleMap[space.id].roleName as SpaceRole, + role: roleMap[space.id].roleName as IRole, })); } diff --git a/apps/nestjs-backend/src/features/table/open-api/table-open-api.service.ts b/apps/nestjs-backend/src/features/table/open-api/table-open-api.service.ts index ce749f4ab..16b270826 100644 --- a/apps/nestjs-backend/src/features/table/open-api/table-open-api.service.ts +++ b/apps/nestjs-backend/src/features/table/open-api/table-open-api.service.ts @@ -1,24 +1,22 @@ import { BadRequestException, NotFoundException, Injectable, Logger } from '@nestjs/common'; import type { - BaseRole, - FieldActions, + FieldAction, IFieldRo, IFieldVo, ILinkFieldOptions, ILookupOptionsVo, IViewRo, - RecordActions, - SpaceRole, - TableActions, - ViewActions, + RecordAction, + IRole, + TableAction, + ViewAction, } from '@teable/core'; import { ActionPrefix, FieldKeyType, FieldType, - RoleType, actionPrefixMap, - getPermissionMap, + getBasePermission, } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import type { @@ -454,7 +452,7 @@ export class TableOpenApiService { } async getPermission(baseId: string, tableId: string): Promise { - let role: SpaceRole | BaseRole | null = await this.permissionService.getRoleByBaseId(baseId); + let role: IRole | null = await this.permissionService.getRoleByBaseId(baseId); if (!role) { const { spaceId } = await this.permissionService.getUpperIdByBaseId(baseId); role = await this.permissionService.getRoleBySpaceId(spaceId); @@ -465,21 +463,21 @@ export class TableOpenApiService { return this.getPermissionByRole(tableId, role); } - async getPermissionByRole(tableId: string, role: SpaceRole | BaseRole) { - const permissionMap = getPermissionMap(RoleType.Base, role); + async getPermissionByRole(tableId: string, role: IRole) { + const permissionMap = getBasePermission(role); const tablePermission = actionPrefixMap[ActionPrefix.Table].reduce( (acc, action) => { acc[action] = permissionMap[action]; return acc; }, - {} as Record + {} as Record ); const viewPermission = actionPrefixMap[ActionPrefix.View].reduce( (acc, action) => { acc[action] = permissionMap[action]; return acc; }, - {} as Record + {} as Record ); const recordPermission = actionPrefixMap[ActionPrefix.Record].reduce( @@ -487,7 +485,7 @@ export class TableOpenApiService { acc[action] = permissionMap[action]; return acc; }, - {} as Record + {} as Record ); const fields = await this.prismaService.field.findMany({ @@ -507,11 +505,11 @@ export class TableOpenApiService { acc[action] = permissionMap[action]; return acc; }, - {} as Record + {} as Record ); return acc; }, - {} as Record> + {} as Record> ); return { diff --git a/apps/nestjs-backend/src/features/table/table-permission.service.ts b/apps/nestjs-backend/src/features/table/table-permission.service.ts index 4edbda8a0..d4ba5d202 100644 --- a/apps/nestjs-backend/src/features/table/table-permission.service.ts +++ b/apps/nestjs-backend/src/features/table/table-permission.service.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; -import type { BaseRole, ExcludeAction, SpaceRole, TableActions } from '@teable/core'; -import { ActionPrefix, RoleType, actionPrefixMap, getPermissionMap } from '@teable/core'; +import type { IBaseRole, ExcludeAction, IRole, TableAction } from '@teable/core'; +import { ActionPrefix, actionPrefixMap, getPermissionMap } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import { pick } from 'lodash'; import { ClsService } from 'nestjs-cls'; @@ -27,7 +27,7 @@ export class TablePermissionService { async getTablePermissionMapByBaseId( baseId: string, tableIds?: string[] - ): Promise, boolean>>> { + ): Promise, boolean>>> { const userId = this.cls.get('user.id'); const base = await this.prismaService .txClient() @@ -50,28 +50,24 @@ export class TablePermissionService { throw new NotFoundException('Collaborator not found'); }); const roleName = collaborator.roleName; - return this.getTablePermissionMapByRole(baseId, roleName as BaseRole, tableIds); + return this.getTablePermissionMapByRole(baseId, roleName as IBaseRole, tableIds); } - async getTablePermissionMapByRole( - baseId: string, - roleName: BaseRole | SpaceRole, - tableIds?: string[] - ) { + async getTablePermissionMapByRole(baseId: string, roleName: IRole, tableIds?: string[]) { const tables = await this.prismaService.txClient().tableMeta.findMany({ where: { baseId, deletedTime: null, id: { in: tableIds } }, }); return tables.reduce( (acc, table) => { acc[table.id] = pick( - getPermissionMap(RoleType.Base, roleName as BaseRole), + getPermissionMap(roleName), actionPrefixMap[ActionPrefix.Table].filter( (action) => action !== 'table|create' - ) as ExcludeAction[] + ) as ExcludeAction[] ); return acc; }, - {} as Record, boolean>> + {} as Record, boolean>> ); } } diff --git a/apps/nestjs-backend/src/features/user/user.service.ts b/apps/nestjs-backend/src/features/user/user.service.ts index cf8e09cae..647eca5fd 100644 --- a/apps/nestjs-backend/src/features/user/user.service.ts +++ b/apps/nestjs-backend/src/features/user/user.service.ts @@ -6,7 +6,7 @@ import { generateSpaceId, generateUserId, minidenticon, - SpaceRole, + Role, } from '@teable/core'; import type { Prisma } from '@teable/db-main-prisma'; import { PrismaService } from '@teable/db-main-prisma'; @@ -74,7 +74,7 @@ export class UserService { await this.prismaService.txClient().collaborator.create({ data: { spaceId: space.id, - roleName: SpaceRole.Owner, + roleName: Role.Owner, userId, createdBy: userId, }, diff --git a/apps/nestjs-backend/src/types/cls.ts b/apps/nestjs-backend/src/types/cls.ts index 48666fee6..649cc66dd 100644 --- a/apps/nestjs-backend/src/types/cls.ts +++ b/apps/nestjs-backend/src/types/cls.ts @@ -1,4 +1,4 @@ -import type { PermissionAction } from '@teable/core'; +import type { Action } from '@teable/core'; import type { Prisma } from '@teable/db-main-prisma'; import type { ClsStore } from 'nestjs-cls'; import type { IFieldInstance } from '../features/field/model/factory'; @@ -23,7 +23,7 @@ export interface IClsStore extends ClsStore { rawOpMaps?: IRawOpMap[]; }; shareViewId?: string; - permissions: PermissionAction[]; + permissions: Action[]; // for share db adapter cookie?: string; oldField?: IFieldInstance; diff --git a/apps/nestjs-backend/test/access-token.e2e-spec.ts b/apps/nestjs-backend/test/access-token.e2e-spec.ts index 1c7834e35..53bad64bf 100644 --- a/apps/nestjs-backend/test/access-token.e2e-spec.ts +++ b/apps/nestjs-backend/test/access-token.e2e-spec.ts @@ -1,6 +1,6 @@ /* eslint-disable sonarjs/no-duplicate-string */ import type { INestApplication } from '@nestjs/common'; -import { SpaceRole } from '@teable/core'; +import { Role } from '@teable/core'; import type { CreateAccessTokenVo, ICreateSpaceVo, @@ -188,7 +188,7 @@ describe('OpenAPI AccessTokenController (e2e)', () => { const spaceId = newUserSpace.id; await newUserAxios.post(urlBuilder(EMAIL_SPACE_INVITATION, { spaceId }), { - role: SpaceRole.Viewer, + role: Role.Viewer, emails: [email], }); diff --git a/apps/nestjs-backend/test/computed-user-field.e2e-spec.ts b/apps/nestjs-backend/test/computed-user-field.e2e-spec.ts index 1c50039df..0feb12f34 100644 --- a/apps/nestjs-backend/test/computed-user-field.e2e-spec.ts +++ b/apps/nestjs-backend/test/computed-user-field.e2e-spec.ts @@ -1,6 +1,6 @@ import type { INestApplication } from '@nestjs/common'; import type { IFieldRo, IFieldVo } from '@teable/core'; -import { FieldKeyType, FieldType, SpaceRole } from '@teable/core'; +import { FieldKeyType, FieldType, Role } from '@teable/core'; import { deleteSpaceCollaborator, emailSpaceInvitation, @@ -245,7 +245,7 @@ describe('Computed user field (e2e)', () => { await emailSpaceInvitation({ spaceId: globalThis.testConfig.spaceId, - emailSpaceInvitationRo: { role: SpaceRole.Creator, emails: ['renameUser@example.com'] }, + emailSpaceInvitationRo: { role: Role.Creator, emails: ['renameUser@example.com'] }, }); table1 = ( await user2Request.post(urlBuilder(CREATE_TABLE, { baseId }), { diff --git a/apps/nestjs-backend/test/invitation.e2e-spec.ts b/apps/nestjs-backend/test/invitation.e2e-spec.ts index adaf6e988..a6a76c5d9 100644 --- a/apps/nestjs-backend/test/invitation.e2e-spec.ts +++ b/apps/nestjs-backend/test/invitation.e2e-spec.ts @@ -1,5 +1,5 @@ import type { INestApplication } from '@nestjs/common'; -import { SpaceRole } from '@teable/core'; +import { Role } from '@teable/core'; import type { CreateSpaceInvitationLinkVo, ListSpaceCollaboratorVo } from '@teable/openapi'; import { ACCEPT_INVITATION_LINK, @@ -38,7 +38,7 @@ describe('OpenAPI InvitationController (e2e)', () => { it('/api/invitation/link/accept (POST)', async () => { const invitationLinkRes = await apiCreateSpaceInvitationLink({ spaceId, - createSpaceInvitationLinkRo: { role: SpaceRole.Owner }, + createSpaceInvitationLinkRo: { role: Role.Owner }, }); const { invitationId, invitationCode } = invitationLinkRes.data as CreateSpaceInvitationLinkVo; @@ -48,6 +48,6 @@ describe('OpenAPI InvitationController (e2e)', () => { const collaborators: ListSpaceCollaboratorVo = (await apiGetSpaceCollaboratorList(spaceId)) .data; const collaborator = collaborators.find(({ email }) => email === 'newuser@example.com'); - expect(collaborator?.role).toEqual(SpaceRole.Owner); + expect(collaborator?.role).toEqual(Role.Owner); }); }); diff --git a/apps/nestjs-backend/test/space.e2e-spec.ts b/apps/nestjs-backend/test/space.e2e-spec.ts index e4b30cf61..f2cfe05b9 100644 --- a/apps/nestjs-backend/test/space.e2e-spec.ts +++ b/apps/nestjs-backend/test/space.e2e-spec.ts @@ -2,7 +2,7 @@ import type { INestApplication } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import type { HttpError } from '@teable/core'; -import { IdPrefix, SpaceRole } from '@teable/core'; +import { IdPrefix, Role } from '@teable/core'; import type { ListSpaceCollaboratorVo, ListSpaceInvitationLinkVo } from '@teable/openapi'; import { createSpace as apiCreateSpace, @@ -115,7 +115,7 @@ describe('OpenAPI SpaceController (e2e)', () => { it('/api/space/:spaceId/invitation/link (POST)', async () => { const res = await apiCreateSpaceInvitationLink({ spaceId, - createSpaceInvitationLinkRo: { role: SpaceRole.Owner }, + createSpaceInvitationLinkRo: { role: Role.Owner }, }); expect(createSpaceInvitationLinkVoSchema.safeParse(res.data).success).toEqual(true); @@ -124,16 +124,16 @@ describe('OpenAPI SpaceController (e2e)', () => { it('/api/space/:spaceId/invitation/link/:invitationId (PATCH)', async () => { const res = await apiCreateSpaceInvitationLink({ spaceId, - createSpaceInvitationLinkRo: { role: SpaceRole.Owner }, + createSpaceInvitationLinkRo: { role: Role.Owner }, }); const newInvitationId = res.data.invitationId; const newSpaceUpdate = await apiUpdateSpaceInvitationLink({ spaceId, invitationId: newInvitationId, - updateSpaceInvitationLinkRo: { role: SpaceRole.Editor }, + updateSpaceInvitationLinkRo: { role: Role.Editor }, }); - expect(newSpaceUpdate.data.role).toEqual(SpaceRole.Editor); + expect(newSpaceUpdate.data.role).toEqual(Role.Editor); await apiDeleteSpaceInvitationLink({ spaceId, invitationId: newInvitationId }); }); @@ -146,7 +146,7 @@ describe('OpenAPI SpaceController (e2e)', () => { it('/api/space/:spaceId/invitation/link/:invitationId (DELETE)', async () => { const res = await apiCreateSpaceInvitationLink({ spaceId, - createSpaceInvitationLinkRo: { role: SpaceRole.Owner }, + createSpaceInvitationLinkRo: { role: Role.Owner }, }); const newInvitationId = res.data.invitationId; @@ -159,7 +159,7 @@ describe('OpenAPI SpaceController (e2e)', () => { it('/api/space/:spaceId/invitation/email (POST)', async () => { await apiEmailSpaceInvitation({ spaceId, - emailSpaceInvitationRo: { role: SpaceRole.Owner, emails: [newUserEmail] }, + emailSpaceInvitationRo: { role: Role.Owner, emails: [newUserEmail] }, }); const collaborators: ListSpaceCollaboratorVo = (await apiGetSpaceCollaboratorList(spaceId)) @@ -168,7 +168,7 @@ describe('OpenAPI SpaceController (e2e)', () => { const newCollaboratorInfo = collaborators.find(({ email }) => email === newUserEmail); expect(newCollaboratorInfo).not.toBeUndefined(); - expect(newCollaboratorInfo?.role).toEqual(SpaceRole.Owner); + expect(newCollaboratorInfo?.role).toEqual(Role.Owner); }); }); }); diff --git a/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx b/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx index cd2f87d13..e6d857868 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/access-token/AccessTokenList.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import type { AllActions } from '@teable/core'; +import type { Action } from '@teable/core'; import { ArrowUpRight, Plus } from '@teable/icons'; import { deleteAccessToken, listAccessToken } from '@teable/openapi'; import { ReactQueryKeys } from '@teable/sdk/config'; @@ -143,7 +143,7 @@ export const AccessTokenList = (props: { newToken?: string }) => { {scopes .slice(0, 2) - .map((action) => actionStaticMap[action as AllActions].description) + .map((action) => actionStaticMap[action as Action].description) .join('; ')} {scopesMoreLen ? ` ${t('token:moreScopes', { len: scopesMoreLen })}` : ''} diff --git a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx index 2bcdcf919..fee0587bf 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/access-token/form/AccessTokenForm.tsx @@ -1,4 +1,4 @@ -import { ActionPrefix, type AllActions } from '@teable/core'; +import { ActionPrefix, type Action } from '@teable/core'; import { createAccessTokenRoSchema, type CreateAccessTokenRo, @@ -152,7 +152,7 @@ export const AccessTokenForm = (props: IAccessTokenForm) diff --git a/apps/nextjs-app/src/features/app/blocks/setting/components/ScopesSelect.tsx b/apps/nextjs-app/src/features/app/blocks/setting/components/ScopesSelect.tsx index 78180cf8b..ddc5fe4dd 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/components/ScopesSelect.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/components/ScopesSelect.tsx @@ -1,36 +1,36 @@ import { actionPrefixMap } from '@teable/core'; -import type { ActionPrefix, AllActions } from '@teable/core'; +import type { ActionPrefix, Action } from '@teable/core'; import { usePermissionActionsStatic } from '@teable/sdk/hooks'; import { Checkbox, Label } from '@teable/ui-lib/shadcn'; import { useMemo, useState } from 'react'; interface IScopesSelectProps { - initValue?: AllActions[]; + initValue?: Action[]; onChange?: (value: string[]) => void; actionsPrefixes?: ActionPrefix[]; } export const ScopesSelect = (props: IScopesSelectProps) => { const { onChange, initValue, actionsPrefixes } = props; - const [value, setValue] = useState>(() => { + const [value, setValue] = useState>(() => { if (initValue) { return initValue.reduce( (acc, cur) => { acc[cur] = true; return acc; }, - {} as Record + {} as Record ); } - return {} as Record; + return {} as Record; }); const { actionPrefixStaticMap, actionStaticMap } = usePermissionActionsStatic(); - const onCheckBoxChange = (status: boolean, val: AllActions) => { + const onCheckBoxChange = (status: boolean, val: Action) => { const actionMap = { ...value }; actionMap[val] = status; setValue(actionMap); - const actions = Object.keys(actionMap).filter((key) => actionMap[key as AllActions]); + const actions = Object.keys(actionMap).filter((key) => actionMap[key as Action]); onChange?.(actions); }; diff --git a/apps/nextjs-app/src/features/app/blocks/setting/oauth-app/manage/OAuthAppForm.tsx b/apps/nextjs-app/src/features/app/blocks/setting/oauth-app/manage/OAuthAppForm.tsx index 39aa7046d..41ccdf6dc 100644 --- a/apps/nextjs-app/src/features/app/blocks/setting/oauth-app/manage/OAuthAppForm.tsx +++ b/apps/nextjs-app/src/features/app/blocks/setting/oauth-app/manage/OAuthAppForm.tsx @@ -1,5 +1,5 @@ import { useMutation } from '@tanstack/react-query'; -import type { AllActions } from '@teable/core'; +import type { Action } from '@teable/core'; import { UploadType, oauthCreateRoSchema, @@ -222,7 +222,7 @@ export const OAuthAppForm = forwardRef((pr updateForm('scopes', value)} /> diff --git a/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx b/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx index 4ff3a28a0..8fe96e220 100644 --- a/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx +++ b/apps/nextjs-app/src/features/app/blocks/space/SpaceCard.tsx @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { SpaceRole } from '@teable/core'; +import { Role } from '@teable/core'; import type { IGetBaseVo, IGetSpaceVo, ISubscriptionSummaryVo } from '@teable/openapi'; import { PinType, deleteSpace, updateSpace } from '@teable/openapi'; import { ReactQueryKeys } from '@teable/sdk/config'; @@ -88,7 +88,7 @@ export const SpaceCard: FC = (props) => { level={subscription?.level} status={subscription?.status} spaceId={space.id} - withUpgrade={space.role === SpaceRole.Owner} + withUpgrade={space.role === Role.Owner} /> )} diff --git a/apps/nextjs-app/src/features/app/blocks/space/SpaceInnerPage.tsx b/apps/nextjs-app/src/features/app/blocks/space/SpaceInnerPage.tsx index 55f9708d1..c3fdd0b12 100644 --- a/apps/nextjs-app/src/features/app/blocks/space/SpaceInnerPage.tsx +++ b/apps/nextjs-app/src/features/app/blocks/space/SpaceInnerPage.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { SpaceRole } from '@teable/core'; +import { Role } from '@teable/core'; import { PinType, deleteSpace, @@ -112,7 +112,7 @@ export const SpaceInnerPage: React.FC = () => { level={subscriptionSummary?.level} status={subscriptionSummary?.status} spaceId={space.id} - withUpgrade={space.role === SpaceRole.Owner} + withUpgrade={space.role === Role.Owner} /> )} diff --git a/apps/nextjs-app/src/features/app/components/collaborator-manage/space-inner/Collaborators.tsx b/apps/nextjs-app/src/features/app/components/collaborator-manage/space-inner/Collaborators.tsx index 8caa28c66..744118642 100644 --- a/apps/nextjs-app/src/features/app/components/collaborator-manage/space-inner/Collaborators.tsx +++ b/apps/nextjs-app/src/features/app/components/collaborator-manage/space-inner/Collaborators.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import type { SpaceRole } from '@teable/core'; +import type { IRole } from '@teable/core'; import { getSpaceCollaboratorList } from '@teable/openapi'; import { ReactQueryKeys } from '@teable/sdk'; import { useTranslation } from 'next-i18next'; @@ -8,7 +8,7 @@ import { UserAvatar } from '@/features/app/components/user/UserAvatar'; interface SpaceInnerCollaboratorProps { spaceId: string; - role?: SpaceRole; + role?: IRole; } export const Collaborators: React.FC = (props) => { diff --git a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Collaborators.tsx b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Collaborators.tsx index e7877cbc9..5d04019ee 100644 --- a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Collaborators.tsx +++ b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Collaborators.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import type { SpaceRole } from '@teable/core'; +import type { IRole } from '@teable/core'; import { hasPermission } from '@teable/core'; import { X } from '@teable/icons'; import type { ListSpaceCollaboratorVo } from '@teable/openapi'; @@ -26,7 +26,7 @@ import { RoleSelect } from './RoleSelect'; interface ICollaborators { spaceId: string; - role: SpaceRole; + role: IRole; } const filterCollaborators = (search: string, collaborators?: ListSpaceCollaboratorVo) => { diff --git a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Invite.tsx b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Invite.tsx index ba45ddff9..877401ec6 100644 --- a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Invite.tsx +++ b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/Invite.tsx @@ -1,5 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { SpaceRole, hasPermission } from '@teable/core'; +import type { IRole } from '@teable/core'; +import { Role, hasPermission } from '@teable/core'; import { X } from '@teable/icons'; import { createSpaceInvitationLink, emailSpaceInvitation } from '@teable/openapi'; import { ReactQueryKeys, useSpaceRoleStatic } from '@teable/sdk'; @@ -14,7 +15,7 @@ import { getRolesWithLowerPermissions } from './utils'; interface IInvite { className?: string; spaceId: string; - role: SpaceRole; + role: IRole; } export const Invite: React.FC = (props) => { @@ -23,7 +24,7 @@ export const Invite: React.FC = (props) => { const { t } = useTranslation('common'); const [inviteType, setInviteType] = useState<'link' | 'email'>('email'); - const [inviteRole, setInviteRole] = useState(role); + const [inviteRole, setInviteRole] = useState(role); const [email, setEmail] = useState(''); const [inviteEmails, setInviteEmails] = useState([]); @@ -54,12 +55,12 @@ export const Invite: React.FC = (props) => { const createInviteLink = async () => { await createInviteLinkRequest({ spaceId, createSpaceInvitationLinkRo: { role: inviteRole } }); - setInviteRole(SpaceRole.Creator); + setInviteRole(Role.Creator); }; const changeInviteType = (inviteType: 'link' | 'email') => { initEmail(); - setInviteRole(SpaceRole.Creator); + setInviteRole(Role.Creator); setInviteType(inviteType); }; diff --git a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/InviteLink.tsx b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/InviteLink.tsx index 502fe5e2c..90de1d6de 100644 --- a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/InviteLink.tsx +++ b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/InviteLink.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import type { SpaceRole } from '@teable/core'; +import type { IRole } from '@teable/core'; import { Copy, X } from '@teable/icons'; import { deleteSpaceInvitationLink, @@ -24,7 +24,7 @@ import { getRolesWithLowerPermissions } from './utils'; interface IInviteLink { spaceId: string; - role: SpaceRole; + role: IRole; } export const InviteLink: React.FC = (props) => { diff --git a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/RoleSelect.tsx b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/RoleSelect.tsx index 4612a0459..305fc90d7 100644 --- a/apps/nextjs-app/src/features/app/components/collaborator-manage/space/RoleSelect.tsx +++ b/apps/nextjs-app/src/features/app/components/collaborator-manage/space/RoleSelect.tsx @@ -1,4 +1,4 @@ -import { SpaceRole } from '@teable/core'; +import { Role, type IRole } from '@teable/core'; import { useSpaceRoleStatic } from '@teable/sdk/hooks'; import { cn, @@ -13,11 +13,11 @@ import { find } from 'lodash'; import React, { useMemo } from 'react'; interface IRoleSelect { className?: string; - value?: SpaceRole; - defaultValue?: SpaceRole; + value?: IRole; + defaultValue?: IRole; disabled?: boolean; - filterRoles?: SpaceRole[]; - onChange?: (value: SpaceRole) => void; + filterRoles?: IRole[]; + onChange?: (value: IRole) => void; } export const RoleSelect: React.FC = (props) => { @@ -37,7 +37,7 @@ export const RoleSelect: React.FC = (props) => { return (