updating components and pages into seperate tables

This commit is contained in:
arpitnath 2023-08-11 14:26:58 +05:30
parent b132098c4c
commit ec58c5cbd0
16 changed files with 442 additions and 69 deletions

View file

@ -60,6 +60,8 @@ export const Container = ({
[JSON.stringify(appDefinition), currentPageId]
);
console.log('----mohaaan components', { components });
const currentState = useCurrentState();
const { appVersionsId, enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
(state) => ({

View file

@ -66,6 +66,7 @@ import { useAppDataActions, useAppDataStore, useAppInfo } from '@/_stores/appDat
import { useMounted } from '@/_hooks/use-mount';
// eslint-disable-next-line import/no-unresolved
import { diff } from 'deep-object-diff';
import { camelizeKeys } from 'humps';
setAutoFreeze(false);
enablePatches();
@ -226,6 +227,7 @@ const EditorComponent = (props) => {
}
if (mounted && didAppDefinitionChanged && currentPageId) {
console.log('----mohaaan: useEffecr', { appDefinition });
const components = appDefinition?.pages[currentPageId]?.components || {};
computeComponentState(components);
@ -345,13 +347,9 @@ const EditorComponent = (props) => {
theme: { name: props?.darkMode ? 'dark' : 'light' },
urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))),
};
const page = {
...props?.currentState?.page,
handle: props?.pageHandle,
variables: {},
};
updateState({ appId: props?.params?.id });
useCurrentStateStore.getState().actions.setCurrentState({ globals, page });
useCurrentStateStore.getState().actions.setCurrentState({ globals });
};
const fetchDataQueries = async (id, selectFirstQuery = false, runQueriesOnAppLoad = false) => {
@ -632,6 +630,29 @@ const EditorComponent = (props) => {
};
//!--------
const buildAppDefinition = (data) => {
const editingVersion = _.omit(camelizeKeys(data.editing_version), ['definition', 'updatedAt', 'createdAt', 'name']);
editingVersion['currentVersionId'] = editingVersion.id;
_.unset(editingVersion, 'id');
const pages = data.pages.reduce((acc, page) => {
acc[page.id] = page;
return acc;
}, {});
const appJSON = {
globalSettings: editingVersion.globalSettings,
homePageId: editingVersion.homePageId,
showHideViewerNavigation: editingVersion.showHideViewerNavigation ?? true,
pages: pages,
};
return appJSON;
};
//****** */
const fetchApp = async (startingPageHandle) => {
const _appId = props?.params?.id;
@ -641,12 +662,30 @@ const EditorComponent = (props) => {
useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id);
await fetchDataSources(data.editing_version?.id);
await fetchDataQueries(data.editing_version?.id, true, true);
console.log('---piku [fetching app]---]', { data, defaultDef: defaultDefinition(props.darkMode) });
let dataDefinition = data.definition ?? defaults(data.definition, defaultDefinition(props.darkMode));
const appDefData = buildAppDefinition(data);
const pages = Object.entries(dataDefinition.pages).map(([pageId, page]) => ({ id: pageId, ...page }));
// let dataDefinition = data.definition ?? defaults(data.definition, defaultDefinition(props.darkMode));
// const pages = Object.entries(dataDefinition.pages).map(([pageId, page]) => ({ id: pageId, ...page }));
// const startingPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id;
// const homePageId = !startingPageHandle || startingPageId === 'null' ? dataDefinition.homePageId : startingPageId;
// !------
const appJson = appDefData;
const pages = data.pages;
const startingPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id;
const homePageId = !startingPageHandle || startingPageId === 'null' ? dataDefinition.homePageId : startingPageId;
const homePageId = !startingPageId || startingPageId === 'null' ? appJson.homePageId : startingPageId;
const currentComponents = appJson.pages[homePageId]?.components ?? {};
console.log('---piku [fetching app] [pages] ==> ', { currentComponents });
const currentpageData = {
handle: appJson.pages[homePageId]?.handle,
name: appJson.pages[homePageId]?.name,
id: homePageId,
variables: {},
};
// !------
setCurrentPageId(homePageId);
@ -662,20 +701,15 @@ const EditorComponent = (props) => {
});
useCurrentStateStore.getState().actions.setCurrentState({
page: {
handle: dataDefinition.pages[homePageId]?.handle,
name: dataDefinition.pages[homePageId]?.name,
id: homePageId,
variables: {},
},
page: currentpageData,
});
updateEditorState({
isLoading: false,
appDefinition: dataDefinition,
appDefinition: appJson,
});
for (const event of dataDefinition.pages[homePageId]?.events ?? []) {
for (const event of appJson.pages[homePageId]?.events ?? []) {
await handleEvent(event.eventId, event);
}
getCanvasWidth();
@ -813,9 +847,7 @@ const EditorComponent = (props) => {
} else if (!isEmpty(props?.editingVersion)) {
const componentDiff = computeAppDiff(appDefinitionDiff, currentPageId, appDiffOptions);
// console.log('---piku [componentDiff]--', componentDiff);
updateAppVersion(appId, props.editingVersion?.id, appDefinition, componentDiff, isUserSwitchedVersion)
updateAppVersion(appId, props.editingVersion?.id, currentPageId, componentDiff, isUserSwitchedVersion)
.then(() => {
const _editingVersion = {
...props.editingVersion,

View file

@ -44,6 +44,7 @@ export const LeftSidebarInspector = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appDefinition['selectedComponent']]);
const currentState = useCurrentState();
const queries = {};
if (!_.isEmpty(dataQueries)) {

View file

@ -1133,7 +1133,7 @@ export function computeComponentState(components = {}) {
Object.keys(components).forEach((key) => {
const component = components[key];
const componentMeta = componentTypes.find((comp) => component.component.component === comp.component);
console.log('------tj: computeComponentState', { component, currentComponents });
const existingComponentName = Object.keys(currentComponents).find((comp) => currentComponents[comp].id === key);
const existingValues = currentComponents[existingComponentName];

View file

@ -7,6 +7,7 @@ export const appVersionService = {
create,
del,
save,
autoSaveApp,
};
function getAll(appId) {
@ -44,7 +45,7 @@ function del(appId, versionId) {
}
function save(appId, versionId, values, isUserSwitchedVersion = false) {
console.log('---piku [version saved]', { values });
// console.log('---piku [version saved]', { values });
const body = { is_user_switched_version: isUserSwitchedVersion };
if (values.definition) body['definition'] = values.definition;
if (values.name) body['name'] = values.name;
@ -58,3 +59,22 @@ function save(appId, versionId, values, isUserSwitchedVersion = false) {
};
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
}
function autoSaveApp(appId, versionId, diff, type, pageId, operation, isUserSwitchedVersion = false) {
console.log('---piku [version saved] [v2]', { operation, type, diff });
const OPERATION = Object.freeze({
create: 'POST',
update: 'PUT',
delete: 'DELETE',
});
const body = { is_user_switched_version: isUserSwitchedVersion, pageId, diff: diff };
const requestOptions = {
method: OPERATION[operation],
headers: authHeader(),
credentials: 'include',
body: JSON.stringify(body),
};
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}/${type}`, requestOptions).then(handleResponse);
}

View file

@ -31,11 +31,15 @@ export const useAppDataStore = create(
updateApps: (apps) => set(() => ({ apps: apps })),
updateState: (state) => set((prev) => ({ ...prev, ...state })),
updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })),
updateAppVersion: async (appId, versionId, appDefinition, appDefinitionDiff, isUserSwitchedVersion = false) => {
return await appVersionService.save(
updateAppVersion: async (appId, versionId, pageId, appDefinitionDiff, isUserSwitchedVersion = false) => {
// console.log('-piku :: from store', { appDefinitionDiff });
return await appVersionService.autoSaveApp(
appId,
versionId,
{ definition: appDefinition, diff: appDefinitionDiff },
appDefinitionDiff.updateDiff,
appDefinitionDiff.type,
pageId,
appDefinitionDiff.operation,
isUserSwitchedVersion
);
},

View file

@ -36,7 +36,7 @@ const defaultComponent = {
const updateType = Object.freeze({
pageDefinitionChanged: 'pages',
containerChanges: 'layout',
containerChanges: 'components/layout',
componentAdded: 'components',
componentDefinitionChanged: 'components',
});
@ -44,6 +44,7 @@ const updateType = Object.freeze({
export const computeAppDiff = (appDiff, currentPageId, opts) => {
let type;
let updateDiff;
let operation = 'update';
if (opts?.pageDefinitionChanged) {
updateDiff = appDiff?.pages[currentPageId];
@ -64,9 +65,10 @@ export const computeAppDiff = (appDiff, currentPageId, opts) => {
result[id] = _.defaultsDeep(metaDiff, defaultComponent);
result[id].layouts = appDiff.pages[currentPageId].components[id].layouts;
operation = 'create';
return result;
// result[id].componentId = id;
// return { ..._.defaultsDeep(metaDiff, defaultComponent), componentId: id };
}, {});
type = updateType.componentDefinitionChanged;
@ -74,5 +76,5 @@ export const computeAppDiff = (appDiff, currentPageId, opts) => {
console.log('---piku [currentPageComponents]', { updateDiff, opts, type });
return { updateDiff, type };
return { updateDiff, type, operation };
};

View file

@ -12,24 +12,31 @@ export class CreateLayoutTable1691007037021 implements MigrationInterface {
isPrimary: true,
default: 'gen_random_uuid()',
},
{
name: 'name',
type: 'varchar',
isNullable: false,
},
{
name: 'type',
type: 'varchar',
type: 'enum',
enumName: 'layput_type',
enum: ['desktop', 'mobile'],
isNullable: false,
},
{
name: 'top',
type: 'integer',
type: 'double precision',
isNullable: false,
},
{
name: 'left',
type: 'integer',
type: 'double precision',
isNullable: false,
},
{
name: 'width',
type: 'double precision',
isNullable: false,
},
{
name: 'height',
type: 'double precision',
isNullable: false,
},
{

View file

@ -29,6 +29,7 @@ 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')
export class AppsController {
@ -36,6 +37,7 @@ export class AppsController {
private appsService: AppsService,
private foldersService: FoldersService,
private componentsService: ComponentsService,
private pageService: PageService,
private appsAbilityFactory: AppsAbilityFactory
) {}
@ -88,9 +90,7 @@ export class AppsController {
? await this.appsService.findDataQueriesForVersion(app.editingVersion.id)
: [];
const pagesForVersion = app.editingVersion ? await this.appsService.findPagesForVersion(app.editingVersion.id) : [];
console.log('------arpit [pagesforVersion]', { pagesForVersion });
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(app.editingVersion.id) : [];
// serialize queries
for (const query of dataQueriesForVersion) {
@ -320,16 +320,87 @@ export class AppsController {
throw new ForbiddenException('You do not have permissions to perform this action');
}
const updateType = versionEditDto.app_diff?.type;
console.log('----arpit apps controller => ', { updateType });
// if(updateType=== 'components') {
// await this.componentsService.createOrUpdate(versionEditDto.app_diff.components, versionId)
// }
// const updateType = versionEditDto.app_diff?.type;
// console.log('----arpit apps controller => ', { updateType });
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');
}
// const updateType = versionEditDto.app_diff?.type;
console.log('----arpit apps controller v2 => ', { versionEditDto });
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');
}
// const updateType = versionEditDto.app_diff?.type;
console.log('----arpit apps controller v2 [update] => ', { versionEditDto });
await this.componentsService.update(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');
}
// const updateType = versionEditDto.app_diff?.type;
console.log('----arpit apps controller v2 |layput | [update] => ', { versionEditDto });
await this.componentsService.componentLayoutChange(versionEditDto.diff);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)

View file

@ -0,0 +1,12 @@
import { Controller } from '@nestjs/common';
// import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
@Controller({
path: 'apps',
version: '2', // Set the version to '2'
})
export class AppsControllerV2 {
constructor(/* Add your services and dependencies here */) {}
// Add your new version 2 methods here
}

View file

@ -22,5 +22,9 @@ export class VersionEditDto {
is_user_switched_version: boolean;
@IsOptional()
app_diff: any;
diff: any;
@IsOptional()
@IsString()
pageId: string;
}

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { Component } from './component.entity';
@Entity({ name: 'layouts' })
@ -6,21 +6,25 @@ export class Layout {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'name' })
name: string;
@Column({ name: 'type' })
@Column({ type: 'enum', enumName: 'layout_type', name: 'type', enum: ['desktop', 'mobile'] })
type: string;
@Column({ name: 'top' })
@Column({ name: 'top', type: 'double precision' })
top: number;
@Column({ name: 'left' })
@Column({ name: 'left', type: 'double precision' })
left: number;
@Column({ name: 'width', type: 'double precision' })
width: number;
@Column({ name: 'height', type: 'double precision' })
height: number;
@Column({ name: 'component_id' })
ComponentId: string;
componentId: string;
@ManyToOne(() => Component, (component) => component.layouts)
@JoinColumn({ name: 'component_id' })
component: Component;
}

View file

@ -39,6 +39,7 @@ import { EventHandler } from 'src/entities/event_handler.entity';
import { Layout } from 'src/entities/layout.entity';
import { ComponentsService } from '@services/components.service';
import { PageService } from '@services/page.service';
@Module({
imports: [
@ -80,6 +81,7 @@ import { ComponentsService } from '@services/components.service';
PluginsHelper,
AppEnvironmentService,
ComponentsService,
PageService,
],
controllers: [AppsController, AppUsersController, AppsImportExportController],
})

View file

@ -386,15 +386,6 @@ export class AppsService {
});
}
async findPagesForVersion(appVersionId: string): Promise<Page[]> {
return await dbTransactionWrap(async (manager: EntityManager) => {
return manager
.createQueryBuilder(Page, 'pages')
.where('pages.appVersionId = :appVersionId', { appVersionId }) // Replace 'AppVersionId' with the actual column name
.getMany();
});
}
async createNewDataSourcesAndQueriesForVersion(
manager: EntityManager,
appVersion: AppVersion,

View file

@ -1,7 +1,10 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Component } from 'src/entities/component.entity'; // Adjust the import path as per your project structure
import { EntityManager, Repository } from 'typeorm';
import { Component } from 'src/entities/component.entity';
import { Layout } from 'src/entities/layout.entity';
import { Page } from 'src/entities/page.entity';
import { dbTransactionWrap } from 'src/helpers/utils.helper';
@Injectable()
export class ComponentsService {
@ -14,7 +17,190 @@ export class ComponentsService {
return this.componentsRepository.findOne(id);
}
async createOrUpdate(componentDiff: any) {
console.log('----arpit:::: component service', { componentDiff });
async create(componentDiff: object, pageId: string) {
return dbTransactionWrap(async (manager: EntityManager) => {
const page = await manager.findOne(Page, pageId);
const newComponents = this.transformComponentData(componentDiff);
const componentLayouts = [];
newComponents.forEach((component) => {
component.page = page;
});
const savedComponents = await manager.save(Component, newComponents);
savedComponents.forEach((component) => {
const componentLayout = componentDiff[component.id].layouts;
if (componentLayout) {
for (const type in componentLayout) {
const layout = componentLayout[type];
const newLayout = new Layout();
newLayout.type = type;
newLayout.top = layout.top;
newLayout.left = layout.left;
newLayout.width = layout.width;
newLayout.height = layout.height;
newLayout.component = component;
componentLayouts.push(newLayout);
}
}
});
await manager.save(Layout, componentLayouts);
return {};
});
}
async update(componentDiff: object) {
return dbTransactionWrap(async (manager) => {
for (const componentId in componentDiff) {
const { component } = componentDiff[componentId];
const componentData = await manager.findOne(Component, componentId);
if (!componentData) {
return {
error: {
message: `Component with id ${componentId} does not exist`,
},
};
}
const isComponentDefinitionChanged = component.definition ? true : false;
if (isComponentDefinitionChanged) {
const updatedDefinition = component.definition;
const columnsUpdated = Object.keys(updatedDefinition);
const newComponentData = columnsUpdated.reduce((acc, column) => {
const newColumnData = {
...componentData[column],
...updatedDefinition[column],
};
acc[column] = newColumnData;
return acc;
}, {});
await manager.update(Component, componentId, newComponentData);
return;
}
await manager.update(Component, componentId, component);
return;
}
});
}
async componentLayoutChange(componenstLayoutDiff: object) {
return dbTransactionWrap(async (manager: EntityManager) => {
for (const componentId in componenstLayoutDiff) {
const { layouts } = componenstLayoutDiff[componentId];
const componentLayout = await manager.findOne(Layout, { componentId });
if (!componentLayout) {
return {
error: {
message: `Component with id ${componentId} does not exist`,
},
};
}
for (const type in layouts) {
const layout = {
type,
...layouts[type],
};
const currentLayout = Object.assign({}, componentLayout);
const newLayout = {
...currentLayout,
...layout,
};
console.log('--arpit [layput changed]', { type, layout, componentLayout, newLayout });
await manager.update(Layout, { id: componentLayout.id }, newLayout);
}
}
});
}
async getAllComponents(pageId: string) {
// need to get all components for a page with their layouts
return dbTransactionWrap(async (manager: EntityManager) => {
return manager
.createQueryBuilder(Component, 'component')
.leftJoinAndSelect('component.layouts', 'layout')
.where('component.pageId = :pageId', { pageId })
.getMany()
.then((components) => {
return components.reduce((acc, component) => {
const componentId = component.id;
const componentData = component;
const componentLayout = component.layouts[0];
const transformedData = this.createComponentWithLayout(componentData, componentLayout);
acc[componentId] = transformedData[componentId];
return acc;
}, {});
});
});
}
transformComponentData(data: object): Component[] {
const transformedComponents: Component[] = [];
for (const componentId in data) {
const componentData = data[componentId];
const transformedComponent: Component = new Component();
transformedComponent.id = componentId;
transformedComponent.name = componentData.name;
transformedComponent.properties = componentData.properties || {};
transformedComponent.styles = componentData.styles || {};
transformedComponent.validations = componentData.validation || {};
transformedComponents.push(transformedComponent);
}
return transformedComponents;
}
createComponentWithLayout(componentData, layoutData) {
const { id, name, properties, styles, validations } = componentData;
const { type, top, left, width, height } = layoutData;
const componentWithLayout = {
[id]: {
component: {
name,
definition: {
properties,
styles,
validations,
},
},
layouts: {
[type]: {
top,
left,
width,
height,
},
},
},
};
return componentWithLayout;
}
}

View file

@ -0,0 +1,35 @@
import { Injectable } from '@nestjs/common';
import { EntityManager, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Page } from 'src/entities/page.entity';
import { ComponentsService } from './components.service';
@Injectable()
export class PageService {
constructor(
private readonly manager: EntityManager,
@InjectRepository(Page)
private readonly pageRepository: Repository<Page>,
private componentsService: ComponentsService
) {}
async findPagesForVersion(appVersionId: string): Promise<Page[]> {
const allPages = await this.pageRepository.find({ appVersionId });
const pagesWithComponents = await Promise.all(
allPages.map(async (page) => {
const components = await this.componentsService.getAllComponents(page.id);
delete page.appVersionId;
return { ...page, components };
})
);
return pagesWithComponents;
}
async findOne(id: string): Promise<Page> {
return this.pageRepository.findOne(id);
}
}