new v2 controllers for apps

This commit is contained in:
arpitnath 2023-08-14 23:26:27 +05:30
parent 08274d53ff
commit c5167203f5
11 changed files with 412 additions and 113 deletions

View file

@ -1161,6 +1161,8 @@ const EditorComponent = (props) => {
return;
}
setCurrentPageId(pageId);
const copyOfAppDefinition = JSON.parse(JSON.stringify(appDefinition));
copyOfAppDefinition.pages[pageId].name = newName;
@ -1192,12 +1194,17 @@ const EditorComponent = (props) => {
name,
handle: newHandle,
components: {},
index: Object.keys(copyOfAppDefinition.pages).length,
index: Object.keys(copyOfAppDefinition.pages).length + 1,
};
setCurrentPageId(newPageId);
appDefinitionChanged(copyOfAppDefinition, { pageDefinitionChanged: true, switchPage: true, pageId: newPageId });
appDefinitionChanged(copyOfAppDefinition, {
pageDefinitionChanged: true,
addNewPage: true,
switchPage: true,
pageId: newPageId,
});
};
const switchPage = (pageId, queryParams = []) => {
@ -1288,6 +1295,7 @@ const EditorComponent = (props) => {
appDefinitionChanged(newAppDefinition, {
pageDefinitionChanged: true,
deletePageRequest: true,
});
toast.success(`${toBeDeletedPage.name} page deleted.`);
@ -1447,7 +1455,7 @@ const EditorComponent = (props) => {
const pagesObj = newSortedPages.reduce((acc, page, index) => {
acc[page.id] = {
...page,
index: index,
index: index + 1,
};
return acc;
}, {});
@ -1458,6 +1466,7 @@ const EditorComponent = (props) => {
appDefinitionChanged(newAppDefinition, {
pageDefinitionChanged: true,
pageSortingChanged: true,
});
};

View file

@ -66,7 +66,15 @@ function autoSaveApp(appId, versionId, diff, type, pageId, operation, isUserSwit
delete: 'DELETE',
});
const body = { is_user_switched_version: isUserSwitchedVersion, pageId, diff: diff };
let body = {};
if ((type === 'pages' && operation === 'create') || operation === 'delete') {
body = {
...diff,
};
} else {
body = { is_user_switched_version: isUserSwitchedVersion, pageId, diff: diff };
}
const requestOptions = {
method: OPERATION[operation],
@ -74,5 +82,5 @@ function autoSaveApp(appId, versionId, diff, type, pageId, operation, isUserSwit
credentials: 'include',
body: JSON.stringify(body),
};
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}/${type}`, requestOptions).then(handleResponse);
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/${type}`, requestOptions).then(handleResponse);
}

View file

@ -48,12 +48,31 @@ export const computeAppDiff = (appDiff, currentPageId, opts) => {
let updateDiff;
let operation = 'update';
console.log('---piku [computeAppDiff]', { appDiff, currentPageId, opts });
if (opts?.deletePageRequest) {
const deletePageId = _.keys(appDiff?.pages).map((pageId) => {
if (appDiff?.pages[pageId]?.pageId === undefined) {
return pageId;
}
})[0];
if (opts?.pageDefinitionChanged) {
updateDiff = {
pageId: deletePageId,
};
type = updateType.pageDefinitionChanged;
operation = 'delete';
} else if (opts?.pageSortingChanged) {
updateDiff = appDiff?.pages;
type = updateType.pageDefinitionChanged;
} else if (opts?.pageDefinitionChanged) {
updateDiff = appDiff?.pages[currentPageId];
type = updateType.pageDefinitionChanged;
if (opts?.addNewPage) {
operation = 'create';
}
} else if (opts?.componentDeleted) {
const currentPageComponents = appDiff?.pages[currentPageId]?.components;

View file

@ -17,6 +17,11 @@ export class CreatePageTable1691004576333 implements MigrationInterface {
type: 'varchar',
isNullable: false,
},
{
name: 'index',
type: 'int',
isNullable: false,
},
{
name: 'page_handle',
type: 'varchar',

View file

@ -28,7 +28,6 @@ import { EntityManager } from 'typeorm';
import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor';
import { AppDecorator } from 'src/decorators/app.decorator';
import { ComponentsService } from '@services/components.service';
import { PageService } from '@services/page.service';
@Controller('apps')
@ -36,7 +35,6 @@ export class AppsController {
constructor(
private appsService: AppsService,
private foldersService: FoldersService,
private componentsService: ComponentsService,
private pageService: PageService,
private appsAbilityFactory: AppsAbilityFactory
) {}
@ -323,100 +321,6 @@ export class AppsController {
await this.appsService.updateVersion(version, versionEditDto, app.organizationId);
return;
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Post(':id/versions/:versionId/components')
async createComponent(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.create(versionEditDto.diff, versionEditDto.pageId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/components')
async updateComponent(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.update(versionEditDto.diff);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/components')
async deleteComponents(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.delete(versionEditDto.diff);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/components/layout')
async updateComponentLayput(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.componentLayoutChange(versionEditDto.diff);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)

View file

@ -1,12 +1,262 @@
import { Controller } from '@nestjs/common';
// import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
import {
Controller,
ForbiddenException,
Get,
Param,
Post,
Put,
Delete,
Query,
UseGuards,
Body,
BadRequestException,
UseInterceptors,
} from '@nestjs/common';
import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
import { AppsService } from '../services/apps.service';
import { camelizeKeys, decamelizeKeys } from 'humps';
import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.factory';
// import { AppAuthGuard } from 'src/modules/auth/app-auth.guard';
import { App } from 'src/entities/app.entity';
import { User } from 'src/decorators/user.decorator';
import { VersionEditDto } from '@dto/version-edit.dto';
import { CreatePageDto, UpdatePageDto } from '@dto/pages.dto';
import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor';
import { AppDecorator } from 'src/decorators/app.decorator';
import { ComponentsService } from '@services/components.service';
import { PageService } from '@services/page.service';
@Controller({
path: 'apps',
version: '2', // Set the version to '2'
version: '2',
})
export class AppsControllerV2 {
constructor(/* Add your services and dependencies here */) {}
constructor(
private appsService: AppsService,
private componentsService: ComponentsService,
private pageService: PageService,
private appsAbilityFactory: AppsAbilityFactory
) {}
// Add your new version 2 methods here
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Get(':id')
async show(@User() user, @AppDecorator() app: App, @Query('access_type') accessType: string) {
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('viewApp', app)) {
throw new ForbiddenException(
JSON.stringify({
organizationId: app.organizationId,
})
);
}
if (accessType === 'edit' && !ability.can('editApp', app)) {
throw new ForbiddenException(
JSON.stringify({
organizationId: app.organizationId,
})
);
}
const response = decamelizeKeys(app);
const seralizedQueries = [];
const dataQueriesForVersion = app.editingVersion
? await this.appsService.findDataQueriesForVersion(app.editingVersion.id)
: [];
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(app.editingVersion.id) : [];
// serialize queries
for (const query of dataQueriesForVersion) {
const decamelizedQuery = decamelizeKeys(query);
decamelizedQuery['options'] = query.options;
seralizedQueries.push(decamelizedQuery);
}
response['data_queries'] = seralizedQueries;
response['definition'] = app.editingVersion?.definition;
response['pages'] = pagesForVersion;
//! if editing version exists, camelize the definition
if (app.editingVersion && app.editingVersion.definition) {
response['editing_version'] = {
...response['editing_version'],
definition: camelizeKeys(app.editingVersion.definition),
};
}
return response;
}
//components api
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Post(':id/versions/:versionId/components')
async createComponent(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.create(versionEditDto.diff, versionEditDto.pageId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/components')
async updateComponent(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.update(versionEditDto.diff);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/components')
async deleteComponents(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.delete(versionEditDto.diff);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/components/layout')
async updateComponentLayout(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.componentLayoutChange(versionEditDto.diff);
}
// pages api
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Post(':id/versions/:versionId/pages')
async createPages(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() createPageDto: CreatePageDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.pageService.createPage(createPageDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/pages')
async updatePages(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() updatePageDto: Partial<UpdatePageDto>
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.pageService.updatePage({ pageId: updatePageDto.pageId, diff: updatePageDto.diff });
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/pages')
async deletePage(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() body) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can('updateVersions', app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
const { pageId } = body;
if (pageId) {
await this.pageService.deletePage(pageId, versionId);
}
}
}

View file

@ -0,0 +1,17 @@
import { IsNumber, IsString } from 'class-validator';
export class CreatePageDto {
@IsString()
name: string;
@IsString()
handle: string;
@IsNumber()
index: number;
}
export class UpdatePageDto {
pageId: string;
diff: Partial<CreatePageDto>;
}

View file

@ -11,7 +11,10 @@ export class Page {
name: string;
@Column({ name: 'page_handle' })
pageHandle: string;
handle: string;
@Column()
index: number;
@Column({ name: 'app_version_id' })
appVersionId: string;

View file

@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { App } from '../../entities/app.entity';
import { File } from '../../entities/file.entity';
import { AppsController } from '../../controllers/apps.controller';
import { AppsControllerV2 } from '../../controllers/apps.controller.v2';
import { AppsService } from '../../services/apps.service';
import { AppVersion } from '../../../src/entities/app_version.entity';
import { DataQuery } from '../../../src/entities/data_query.entity';
@ -83,6 +84,6 @@ import { PageService } from '@services/page.service';
ComponentsService,
PageService,
],
controllers: [AppsController, AppUsersController, AppsImportExportController],
controllers: [AppsController, AppsControllerV2, AppUsersController, AppsImportExportController],
})
export class AppsModule {}

View file

@ -123,8 +123,9 @@ export class AppsService {
const defaultHomePage = await manager.save(
manager.create(Page, {
name: 'Defualt Page',
pageHandle: 'defaultpage',
handle: 'defaultpage',
appVersionId: appVersion.id,
index: 1,
})
);

View file

@ -4,15 +4,18 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Page } from 'src/entities/page.entity';
import { ComponentsService } from './components.service';
import { CreatePageDto, UpdatePageDto } from '@dto/pages.dto';
import { AppsService } from './apps.service';
import { dbTransactionWrap } from 'src/helpers/utils.helper';
@Injectable()
export class PageService {
constructor(
private readonly manager: EntityManager,
@InjectRepository(Page)
private readonly pageRepository: Repository<Page>,
private componentsService: ComponentsService
private componentsService: ComponentsService,
private appService: AppsService
) {}
async findPagesForVersion(appVersionId: string): Promise<Page[]> {
@ -32,4 +35,83 @@ export class PageService {
async findOne(id: string): Promise<Page> {
return this.pageRepository.findOne(id);
}
async createPage(page: CreatePageDto, appVersionId: string): Promise<Page> {
const newPage = {
...page,
appVersionId: appVersionId,
};
return this.pageRepository.save(newPage);
}
async updatePage(pageUpdates: UpdatePageDto) {
if (Object.keys(pageUpdates.diff).length > 1) {
return this.updatePagesOrder(pageUpdates.diff);
}
const currentPage = await this.pageRepository.findOne(pageUpdates.pageId);
if (!currentPage) {
throw new Error('Page not found');
}
return this.pageRepository.update(pageUpdates.pageId, pageUpdates.diff);
}
async updatePagesOrder(pages) {
const pagesToPage = Object.keys(pages).map((pageId) => {
return {
id: pageId,
index: pages[pageId].index,
};
});
return await dbTransactionWrap(async (manager: EntityManager) => {
await Promise.all(
pagesToPage.map(async (page) => {
await manager.update(Page, page.id, page);
})
);
});
}
async deletePage(pageId: string, appVersionId: string) {
const pageExists = await this.pageRepository.findOne(pageId);
const { editingVersion } = await this.appService.findAppFromVersion(appVersionId);
if (!pageExists) {
throw new Error('Page not found');
}
if (editingVersion?.homePageId === pageId) {
throw new Error('Cannot delete home page');
}
const pageDeletedIndex = pageExists.index;
const pageDeleted = await this.pageRepository.delete(pageId);
if (pageDeleted.affected === 0) {
throw new Error('Page not deleted');
}
const pages = await this.pageRepository.find({ appVersionId: pageExists.appVersionId });
const rearrangedPages = this.rearrangePagesOnDelete(pages, pageDeletedIndex);
await this.pageRepository.save(rearrangedPages);
}
rearrangePagesOnDelete(pages: Page[], pageDeletedIndex: number) {
const rearrangedPages = pages.map((page, index) => {
if (index + 1 >= pageDeletedIndex) {
return {
...page,
index: page.index - 1,
};
}
return page;
});
return rearrangedPages;
}
}