Browse Source

feat: add views apis

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/5444/head
Pranav C 2 years ago
parent
commit
bfc9c9d7af
  1. 3
      packages/nocodb-nest/src/app.module.ts
  2. 3
      packages/nocodb-nest/src/connection/connection.ts
  3. 10
      packages/nocodb-nest/src/middlewares/extract-project-id/extract-project-id.middleware.ts
  4. 14
      packages/nocodb-nest/src/modules/auth/auth.controller.ts
  5. 3
      packages/nocodb-nest/src/modules/tables/tables.module.ts
  6. 5
      packages/nocodb-nest/src/modules/users/users.controller.ts
  7. 20
      packages/nocodb-nest/src/modules/views/views.controller.spec.ts
  8. 129
      packages/nocodb-nest/src/modules/views/views.controller.ts
  9. 11
      packages/nocodb-nest/src/modules/views/views.module.ts
  10. 18
      packages/nocodb-nest/src/modules/views/views.service.spec.ts
  11. 88
      packages/nocodb-nest/src/modules/views/views.service.ts
  12. 27
      packages/nocodb-nest/src/strategies/jwt.strategy.ts
  13. 3
      packages/nocodb-nest/src/strategies/local.strategy.ts
  14. 4
      packages/nocodb-nest/src/utils/extractRolesObj.ts

3
packages/nocodb-nest/src/app.module.ts

@ -10,9 +10,10 @@ import { ProjectsModule } from './modules/projects/projects.module';
import { JwtStrategy } from './strategies/jwt.strategy'; import { JwtStrategy } from './strategies/jwt.strategy';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { TablesModule } from './modules/tables/tables.module'; import { TablesModule } from './modules/tables/tables.module';
import { ViewsModule } from './modules/views/views.module';
@Module({ @Module({
imports: [AuthModule, UsersModule, UtilsModule, ProjectsModule, TablesModule], imports: [AuthModule, UsersModule, UtilsModule, ProjectsModule, TablesModule, ViewsModule],
controllers: [], controllers: [],
providers: [Connection, MetaService, JwtStrategy, ExtractProjectIdMiddleware], providers: [Connection, MetaService, JwtStrategy, ExtractProjectIdMiddleware],
exports: [Connection, MetaService], exports: [Connection, MetaService],

3
packages/nocodb-nest/src/connection/connection.ts

@ -1,6 +1,7 @@
import { Global, Injectable, OnModuleInit } from '@nestjs/common'; import { Global, Injectable, OnModuleInit } from '@nestjs/common';
import * as knex from 'knex'; import * as knex from 'knex';
import { XKnex } from '../db/CustomKnex'
import NcConfigFactory from '../utils/NcConfigFactory'; import NcConfigFactory from '../utils/NcConfigFactory';
@Global() @Global()
@ -20,7 +21,7 @@ export class Connection implements OnModuleInit {
// init metadb connection // init metadb connection
async onModuleInit(): Promise<void> { async onModuleInit(): Promise<void> {
this._config = await NcConfigFactory.make(); this._config = await NcConfigFactory.make();
this.knex = knex.default({ this.knex = XKnex({
...this._config.meta.db, ...this._config.meta.db,
useNullAsDefault: true, useNullAsDefault: true,
}); });

10
packages/nocodb-nest/src/middlewares/extract-project-id/extract-project-id.middleware.ts

@ -26,6 +26,7 @@ import {
Sort, Sort,
View, View,
} from '../../models'; } from '../../models';
import extractRolesObj from '../../utils/extractRolesObj';
import projectAcl from '../../utils/projectAcl'; import projectAcl from '../../utils/projectAcl';
import catchError, { NcError } from '../catchError'; import catchError, { NcError } from '../catchError';
import extractProjectIdAndAuthenticate from '../extractProjectIdAndAuthenticate'; import extractProjectIdAndAuthenticate from '../extractProjectIdAndAuthenticate';
@ -184,9 +185,7 @@ export class ExtractProjectIdMiddleware implements NestMiddleware, CanActivate {
// ); // );
} }
async canActivate( async canActivate(context: ExecutionContext): Promise<boolean> {
context: ExecutionContext,
): Promise<boolean> {
await this.use( await this.use(
context.switchToHttp().getRequest(), context.switchToHttp().getRequest(),
context.switchToHttp().getResponse(), context.switchToHttp().getResponse(),
@ -221,10 +220,7 @@ export class AclMiddleware implements NestInterceptor {
const res = context.switchToHttp().getResponse(); const res = context.switchToHttp().getResponse();
req.customProperty = 'This is a custom property'; req.customProperty = 'This is a custom property';
const roles = req.user.roles.split(',').reduce((acc, role) => { const roles: Record<string, boolean> = extractRolesObj(req.user.roles);
acc[role] = true;
return acc;
}, {});
if (req?.session?.passport?.user?.is_api_token && blockApiTokenAccess) { if (req?.session?.passport?.user?.is_api_token && blockApiTokenAccess) {
NcError.forbidden('Not allowed with API token'); NcError.forbidden('Not allowed with API token');

14
packages/nocodb-nest/src/modules/auth/auth.controller.ts

@ -1,6 +1,7 @@
import extractRolesObj from '../../utils/extractRolesObj'
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { Controller, Request, Post, UseGuards, Body } from "@nestjs/common"; import { Controller, Request, Post, UseGuards, Body, Get } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
@ -28,4 +29,15 @@ export class AuthController {
} }
@UseGuards(AuthGuard('jwt'))
@Get('/api/v1/auth/user/me')
async me(@Request() req) {
const user = {
...req.user,
roles: extractRolesObj(req.user.roles)
}
return user
}
} }

3
packages/nocodb-nest/src/modules/tables/tables.module.ts

@ -4,6 +4,7 @@ import { TablesController } from './tables.controller';
@Module({ @Module({
controllers: [TablesController], controllers: [TablesController],
providers: [TablesService] providers: [TablesService],
exports: [TablesService]
}) })
export class TablesModule {} export class TablesModule {}

5
packages/nocodb-nest/src/modules/users/users.controller.ts

@ -7,9 +7,4 @@ export class UsersController {
constructor(private readonly usersService: UsersService) { constructor(private readonly usersService: UsersService) {
} }
@UseGuards(AuthGuard('jwt'))
@Get('/api/v1/auth/user/me')
async me(@Request() req) {
return req.user
}
} }

20
packages/nocodb-nest/src/modules/views/views.controller.spec.ts

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ViewsController } from './views.controller';
import { ViewsService } from './views.service';
describe('ViewsController', () => {
let controller: ViewsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ViewsController],
providers: [ViewsService],
}).compile();
controller = module.get<ViewsController>(ViewsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

129
packages/nocodb-nest/src/modules/views/views.controller.ts

@ -0,0 +1,129 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
Request,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ViewUpdateReqType } from 'nocodb-sdk';
import { PagedResponseImpl } from '../../helpers/PagedResponse';
import {
ExtractProjectIdMiddleware,
UseAclMiddleware,
} from '../../middlewares/extract-project-id/extract-project-id.middleware';
import { ViewsService } from './views.service';
@Controller()
@UseGuards(ExtractProjectIdMiddleware, AuthGuard('jwt'))
export class ViewsController {
constructor(private readonly viewsService: ViewsService) {}
@Get('/api/v1/db/meta/tables/:tableId/views')
@UseAclMiddleware({
permissionName: 'viewList',
})
async viewList(@Param('tableId') tableId: string, @Request() req) {
return new PagedResponseImpl(
await this.viewsService.viewList({
tableId,
user: req.user,
}),
);
}
@Patch('/api/v1/db/meta/views/:viewId')
@UseAclMiddleware({
permissionName: 'viewUpdate',
})
async viewUpdate(
@Param('viewId') viewId: string,
@Body() body: ViewUpdateReqType,
) {
const result = await this.viewsService.viewUpdate({
viewId,
view: body,
});
return result;
}
@Delete('/api/v1/db/meta/views/:viewId')
@UseAclMiddleware({
permissionName: 'viewDelete',
})
async viewDelete(@Param('viewId') viewId: string) {
const result = await this.viewsService.viewDelete({ viewId });
return result;
}
@Post('/api/v1/db/meta/views/:viewId/show-all')
@UseAclMiddleware({
permissionName: 'showAllColumns',
})
async showAllColumns(
@Param('viewId') viewId: string,
@Query('ignoreIds') ignoreIds: string[],
) {
return await this.viewsService.showAllColumns({
viewId,
ignoreIds,
});
}
@Post('/api/v1/db/meta/views/:viewId/hide-all')
@UseAclMiddleware({
permissionName: 'hideAllColumns',
})
async hideAllColumns(
@Param('viewId') viewId: string,
@Query('ignoreIds') ignoreIds: string[],
) {
return await this.viewsService.hideAllColumns({
viewId,
ignoreIds,
});
}
@Post('/api/v1/db/meta/views/:viewId/share')
@UseAclMiddleware({
permissionName: 'shareView',
})
async shareView(@Param('viewId') viewId: string) {
return await this.viewsService.shareView({ viewId });
}
@Get('/api/v1/db/meta/tables/:tableId/share')
async shareViewList(@Param('tableId') tableId: string) {
return new PagedResponseImpl(
await this.viewsService.shareViewList({
tableId,
}),
);
}
@Patch('/api/v1/db/meta/views/:viewId/share')
@UseAclMiddleware({
permissionName: 'shareViewUpdate',
})
async shareViewUpdate(
@Param('viewId') viewId: string,
@Body() body: ViewUpdateReqType,
) {
return await this.viewsService.shareViewUpdate({
viewId,
sharedView: body,
});
}
@Delete('/api/v1/db/meta/views/:viewId/share')
@UseAclMiddleware({
permissionName: 'shareViewDelete',
})
async shareViewDelete(@Param('viewId') viewId: string) {
return await this.viewsService.shareViewDelete({ viewId });
}
}

11
packages/nocodb-nest/src/modules/views/views.module.ts

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TablesModule } from '../tables/tables.module'
import { ViewsService } from './views.service';
import { ViewsController } from './views.controller';
@Module({
controllers: [ViewsController],
providers: [ViewsService],
imports: [TablesModule],
})
export class ViewsModule {}

18
packages/nocodb-nest/src/modules/views/views.service.spec.ts

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ViewsService } from './views.service';
describe('ViewsService', () => {
let service: ViewsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ViewsService],
}).compile();
service = module.get<ViewsService>(ViewsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

88
packages/nocodb-nest/src/modules/views/views.service.ts

@ -0,0 +1,88 @@
import { Injectable } from '@nestjs/common';
import { SharedViewReqType, ViewUpdateReqType } from 'nocodb-sdk';
import { validatePayload } from '../../helpers';
import { Model, View } from '../../models'
import { T } from 'nc-help';
import { TablesService } from '../tables/tables.service';
@Injectable()
export class ViewsService {
constructor(private tablesService: TablesService) {}
async viewList(param: {
tableId: string;
user: {
roles: Record<string, boolean>;
};
}) {
const model = await Model.get(param.tableId);
const viewList = await this.tablesService.xcVisibilityMetaGet({
projectId: model.project_id,
models: [model],
});
// todo: user roles
//await View.list(param.tableId)
const filteredViewList = viewList.filter((view: any) => {
return Object.keys(param?.user?.roles).some(
(role) => param?.user?.roles[role] && !view.disabled[role],
);
});
return filteredViewList;
}
async shareView(param: { viewId: string }) {
T.emit('evt', { evt_type: 'sharedView:generated-link' });
return await View.share(param.viewId);
}
async viewUpdate(param: { viewId: string; view: ViewUpdateReqType }) {
validatePayload(
'swagger.json#/components/schemas/ViewUpdateReq',
param.view,
);
const result = await View.update(param.viewId, param.view);
T.emit('evt', { evt_type: 'vtable:updated', show_as: result.type });
return result;
}
async viewDelete(param: { viewId: string }) {
await View.delete(param.viewId);
T.emit('evt', { evt_type: 'vtable:deleted' });
return true;
}
async shareViewUpdate(param: {
viewId: string;
sharedView: SharedViewReqType;
}) {
validatePayload(
'swagger.json#/components/schemas/SharedViewReq',
param.sharedView,
);
T.emit('evt', { evt_type: 'sharedView:updated' });
return await View.update(param.viewId, param.sharedView);
}
async shareViewDelete(param: { viewId: string }) {
T.emit('evt', { evt_type: 'sharedView:deleted' });
await View.sharedViewDelete(param.viewId);
return true;
}
async showAllColumns(param: { viewId: string; ignoreIds?: string[] }) {
await View.showAllColumns(param.viewId, param.ignoreIds || []);
return true;
}
async hideAllColumns(param: { viewId: string; ignoreIds?: string[] }) {
await View.hideAllColumns(param.viewId, param.ignoreIds || []);
return true;
}
async shareViewList(param: { tableId: string }) {
return await View.shareViewList(param.tableId);
}
}

27
packages/nocodb-nest/src/strategies/jwt.strategy.ts

@ -4,10 +4,8 @@ import { Strategy, ExtractJwt } from 'passport-jwt';
import { OrgUserRoles } from '../../../nocodb-sdk'; import { OrgUserRoles } from '../../../nocodb-sdk';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import { ProjectUser, User } from '../models'; import { ProjectUser, User } from '../models';
import { import extractRolesObj from '../utils/extractRolesObj';
CacheGetType, import { CacheGetType, CacheScope } from '../utils/globals';
CacheScope,
} from '../utils/globals';
import { jwtConstants } from '../modules/auth/constants'; import { jwtConstants } from '../modules/auth/constants';
import { UsersService } from '../modules/users/users.service'; import { UsersService } from '../modules/users/users.service';
@ -24,7 +22,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
}); });
} }
async validate(req:any,jwtPayload: any,) { async validate(req: any, jwtPayload: any) {
// todo: improve this // todo: improve this
if ( if (
req.ncProjectId && req.ncProjectId &&
@ -33,7 +31,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
return User.getByEmail(jwtPayload?.email).then(async (user) => { return User.getByEmail(jwtPayload?.email).then(async (user) => {
return { return {
...user, ...user,
roles: `owner,creator,${OrgUserRoles.SUPER_ADMIN}`, roles: extractRolesObj(`owner,creator,${OrgUserRoles.SUPER_ADMIN}`),
}; };
}); });
} }
@ -49,7 +47,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
); );
if (cachedVal) { if (cachedVal) {
/*todo: tobe fixed /*todo: tobe fixed
if ( if (
!cachedVal.token_version || !cachedVal.token_version ||
!jwtPayload.token_version || !jwtPayload.token_version ||
@ -60,8 +58,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
return cachedVal; return cachedVal;
} }
return User.getByEmail(jwtPayload?.email).then(async (user: User) => { return User.getByEmail(jwtPayload?.email).then(
/* async (user: { roles: any; id:string }) => {
user.roles = extractRolesObj(user?.roles);
/*
todo: tobe fixed todo: tobe fixed
if ( if (
// !user.token_version || // !user.token_version ||
@ -78,8 +78,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
return ProjectUser.get(req.ncProjectId, user.id).then( return ProjectUser.get(req.ncProjectId, user.id).then(
async (projectUser) => { async (projectUser) => {
user.roles = projectUser?.roles || user.roles; user.roles = extractRolesObj(projectUser?.roles || user.roles);
user.roles = user.roles === 'owner' ? 'owner,creator' : user.roles; user.roles = extractRolesObj(
user.roles === 'owner' ? 'owner,creator' : user.roles,
);
// + (user.roles ? `,${user.roles}` : ''); // + (user.roles ? `,${user.roles}` : '');
await NocoCache.set(`${CacheScope.USER}:${key}`, user); await NocoCache.set(`${CacheScope.USER}:${key}`, user);
@ -95,6 +97,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
throw new Error('User not found'); throw new Error('User not found');
} }
} }
}); },
);
} }
} }

3
packages/nocodb-nest/src/strategies/local.strategy.ts

@ -2,6 +2,7 @@ import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from '../modules/auth/auth.service'; import { AuthService } from '../modules/auth/auth.service';
import extractRolesObj from '../utils/extractRolesObj'
@Injectable() @Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) { export class LocalStrategy extends PassportStrategy(Strategy) {
@ -17,6 +18,8 @@ export class LocalStrategy extends PassportStrategy(Strategy) {
if (!user) { if (!user) {
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
user.roles = extractRolesObj(user.roles)
return user; return user;
} }
} }

4
packages/nocodb-nest/src/utils/extractRolesObj.ts

@ -1,4 +1,8 @@
export default (roles: string | string[]) => { export default (roles: string | string[]) => {
if(!roles) return {};
if(typeof roles === 'object' && !Array.isArray(roles)) return roles;
if (typeof roles === 'string') { if (typeof roles === 'string') {
roles = roles.split(','); roles = roles.split(',');
} }

Loading…
Cancel
Save