appdefinition refactor/cleanup (#7872)

* cleanup controllers and request calls from frontend

* removing unwanted console logs and unused variables

* revering v1 apis og

* adding length validation for page dto

* adding dtos for components

* updated dtos for components and pages

* added dto for event handlers

* fixes event handler dto

* fixes: page dto

* adds/fixes event handlers creating dtp

* fixes: event handler service and dtos
This commit is contained in:
Arpit 2023-10-14 16:25:20 +05:30 committed by GitHub
parent 790681f921
commit 942c700275
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 420 additions and 205 deletions

View file

@ -23,7 +23,7 @@ const AppLoaderComponent = (props) => {
const loadAppDetails = () => {
appService
.getApp(appId, 'edit')
.fetchApp(appId, 'edit')
.then((data) => {
setShouldLoadApp(true);
updateState({

View file

@ -44,7 +44,7 @@ export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion
const selectVersion = (id) => {
appVersionService
.getOne(appId, id)
.getAppVersionData(appId, id)
.then((data) => {
const isCurrentVersionReleased = data.currentVersionId ? true : false;
setAppDefinitionFromVersion(data, isCurrentVersionReleased);
@ -83,7 +83,7 @@ export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion
resetDeleteModal();
});
};
//this
const options = appVersions.map((appVersion) => ({
value: appVersion.id,
isReleasedVersion: appVersion.id === releasedVersionId,

View file

@ -2,7 +2,7 @@ import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
import React from 'react';
const ClientServerSwitch = ({ value, onChange, cyLabel, meta, paramName }) => {
const ClientServerSwitch = ({ value, onChange, meta }) => {
const options = meta?.options;
const defaultValue = value ? 'serverSide' : 'clientSide';
const handleChange = (_value) => {

View file

@ -124,7 +124,7 @@ const EditorComponent = (props) => {
const [currentPageId, setCurrentPageId] = useState(null);
const [zoomLevel, setZoomLevel] = useState(1);
const [isQueryPaneDragging, setIsQueryPaneDragging] = useState(false);
const [isQueryPaneExpanded, setIsQueryPaneExpanded] = useState(false);
const [isQueryPaneExpanded, setIsQueryPaneExpanded] = useState(false); //!check where this is used
const [selectionInProgress, setSelectionInProgress] = useState(false);
const [hoveredComponent, setHoveredComponent] = useState(null);
const [editorMarginLeft, setEditorMarginLeft] = useState(0);
@ -157,15 +157,13 @@ const EditorComponent = (props) => {
useEffect(() => {
updateState({ isLoading: true });
// 1. Get the current session and current user from the authentication service
const currentSession = authenticationService.currentSessionValue;
const currentUser = currentSession?.current_user;
// 2. Subscribe to changes in the current session using RxJS observable pattern
// Subscribe to changes in the current session using RxJS observable pattern
const subscription = authenticationService.currentSession.subscribe((currentSession) => {
// 3. Check if the current user and group permissions are available
if (currentUser && currentSession?.group_permissions) {
// 4. Prepare user details in a format suitable for the application
const userVars = {
email: currentUser.email,
firstName: currentUser.first_name,
@ -239,8 +237,6 @@ const EditorComponent = (props) => {
useEffect(() => {
// This effect runs when lastKeyPressTimestamp changes
// You can place your database update logic here
// Ensure that you only update the database if the timestamp is recent
if (Date.now() - lastKeyPressTimestamp < 500) {
updateEditorState({
isUpdatingEditorStateInProcess: true,
@ -352,6 +348,7 @@ const EditorComponent = (props) => {
});
};
//! websocket events do not work
const initEventListeners = () => {
socket?.addEventListener('message', (event) => {
const data = event.data.replace(/^"(.+(?="$))"$/, '$1');
@ -368,7 +365,6 @@ const EditorComponent = (props) => {
window.addEventListener('message', handleMessage);
await fetchApp(props.params.pageHandle, true);
await fetchApps(0);
await fetchOrgEnvironmentVariables();
await fetchOrgEnvironmentConstants();
@ -495,7 +491,6 @@ const EditorComponent = (props) => {
const handleQueryPaneDragging = (bool) => setIsQueryPaneDragging(bool);
const handleQueryPaneExpanding = (bool) => setIsQueryPaneExpanded(bool);
//! Needs attention
const handleOnComponentOptionChanged = (component, optionName, value) => {
return onComponentOptionChanged(component, optionName, value);
};
@ -657,15 +652,8 @@ const EditorComponent = (props) => {
appDefinitionChanged(newAppDefinition, {
globalSettings: true,
});
// props.ymap?.set('appDef', {
// newDefinition: appDefinition,
// editingVersionId: props.editingVersion?.id,
// });
// autoSave();
};
//!--------
const callBack = async (data, startingPageHandle, versionSwitched = false) => {
setWindowTitle(data.name);
useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version);
@ -724,28 +712,19 @@ const EditorComponent = (props) => {
await handleEvent('onPageLoad', currentPageEvents);
};
//****** */
const fetchApp = async (startingPageHandle, onMount = false) => {
const _appId = props?.params?.id;
if (!onMount) {
await appService.getApp(_appId).then((data) => callBack(data, startingPageHandle));
await appService.fetchApp(_appId).then((data) => callBack(data, startingPageHandle));
} else {
callBack(app, startingPageHandle);
}
};
// !--------
const setAppDefinitionFromVersion = (appData, isCurrentVersionReleased = true) => {
const setAppDefinitionFromVersion = (appData) => {
const version = appData?.editing_version?.id;
if (version?.id !== editingVersion?.id) {
// !Need to fix this
// appDefinitionChanged(defaults(version.definition, defaultDefinition(props.darkMode)), {
// skipAutoSave: true,
// skipYmapUpdate: true,
// versionChanged: true,
// });
if (version?.id === currentVersionId) {
updateEditorState({
canUndo: false,
@ -758,7 +737,6 @@ const EditorComponent = (props) => {
});
callBack(appData, null, true);
initComponentVersioning();
}
};
@ -813,8 +791,6 @@ const EditorComponent = (props) => {
const diffPatches = diff(appDefinition, updatedAppDefinition);
console.log('---arpit::---[diffPatches]', { diffPatches });
const inversePatches = diff(updatedAppDefinition, appDefinition);
const shouldUpdate = !_.isEmpty(diffPatches) && !isEqual(appDefinitionDiff, diffPatches);
@ -918,7 +894,7 @@ const EditorComponent = (props) => {
isUpdatingEditorStateInProcess: false,
});
} else if (!isEmpty(editingVersion)) {
// param diff ofr table columns needs the complte column data or else the json structure is not correct computeComponentPropertyDiff function handles this
//! The computeComponentPropertyDiff function manages the calculation of differences in table columns by requiring complete column data. Without this complete data, the resulting JSON structure may be incorrect.
const paramDiff = computeComponentPropertyDiff(appDefinitionDiff, appDefinition, appDiffOptions);
const updateDiff = computeAppDiff(paramDiff, currentPageId, appDiffOptions, currentLayout);
@ -1105,7 +1081,6 @@ const EditorComponent = (props) => {
const removeComponent = (componentId) => {
if (!isVersionReleased) {
let newDefinition = cloneDeep(appDefinition);
// Delete child components when parent is deleted
let childComponents = [];
@ -1223,7 +1198,7 @@ const EditorComponent = (props) => {
icon: '🗑️',
});
}
// appDefinitionChanged(newDefinition);
handleInspectorView();
} else if (isVersionReleased) {
useAppVersionStore.getState().actions.enableReleasedVersionPopupState();
@ -1258,6 +1233,11 @@ const EditorComponent = (props) => {
return;
}
if (name.length > 32) {
toast.error('Page name cannot be more than 32 characters');
return;
}
const pageHandles = Object.values(appDefinition.pages).map((page) => page.handle);
let newHandle = handle;
@ -1541,8 +1521,6 @@ const EditorComponent = (props) => {
});
};
// !-------
const appVersionPreviewLink = editingVersion
? `/applications/${appId}/versions/${editingVersion.id}/${currentState.page.handle}`
: '';
@ -1583,6 +1561,7 @@ const EditorComponent = (props) => {
);
}
//! Need to move conditionally rendered components to separate components => Widget Manger or Widget Inspector
const shouldrenderWidgetInspector =
currentSidebarTab === 1 &&
selectedComponents?.length === 1 &&
@ -1614,7 +1593,6 @@ const EditorComponent = (props) => {
<EditorHeader
darkMode={props.darkMode}
appDefinition={_.cloneDeep(appDefinition)}
// toggleAppMaintenance={toggleAppMaintenance}
editingVersion={editingVersion}
appVersionPreviewLink={appVersionPreviewLink}
canUndo={canUndo}

View file

@ -29,7 +29,6 @@ export default function EditorHeader({
onVersionRelease,
saveEditingVersion,
onVersionDelete,
isMaintenanceOn,
slug,
darkMode,
}) {

View file

@ -12,11 +12,7 @@ import { componentTypes } from '../WidgetManager/components';
import Select from '@/_ui/Select';
import defaultStyles from '@/_ui/Select/styles';
import { useTranslation } from 'react-i18next';
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
import AddRectangle from '@/_ui/Icon/bulkIcons/AddRectangle';
import { Tooltip } from 'react-tooltip';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import RunjsParameters from './ActionConfigurationPanels/RunjsParamters';
import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore';
import { isQueryRunnable } from '@/_helpers/utils';
@ -24,7 +20,6 @@ import { shallow } from 'zustand/shallow';
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
import NoListItem from './Components/Table/NoListItem';
import ManageEventButton from './ManageEventButton';
// eslint-disable-next-line import/no-unresolved
export const EventManager = ({
sourceId,
@ -37,7 +32,7 @@ export const EventManager = ({
pages,
hideEmptyEventsAlert,
callerQueryId,
customEventRefs = {},
customEventRefs = undefined,
}) => {
const dataQueries = useDataQueriesStore(({ dataQueries = [] }) => {
if (callerQueryId) {
@ -915,7 +910,7 @@ export const EventManager = ({
{events.map((event, index) => {
const actionMeta = ActionTypes.find((action) => action.id === event.event.actionId);
const rowClassName = `card-body p-0 ${focusedEventIndex === index ? ' bg-azure-lt' : ''}`;
// const rowClassName = `card-body p-0 ${focusedEventIndex === index ? ' bg-azure-lt' : ''}`;
return (
<Draggable key={index} draggableId={`${event.eventId}-${index}`} index={index}>
{renderDraggable((provided, snapshot) => {

View file

@ -14,7 +14,7 @@ import { EventManager } from '@/Editor/Inspector/EventManager';
import { staticDataSources, customToggles, mockDataQueryAsComponent } from '../constants';
import { DataSourceTypes } from '../../DataSourceManager/SourceComponents';
import { useDataSources, useGlobalDataSources } from '@/_stores/dataSourcesStore';
import { useDataQueriesActions, useDataQueriesStore } from '@/_stores/dataQueriesStore';
import { useDataQueriesActions } from '@/_stores/dataQueriesStore';
import { useSelectedQuery, useSelectedDataSource } from '@/_stores/queryPanelStore';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';

View file

@ -8,13 +8,7 @@ import { ConfirmDialog } from '@/_components/ConfirmDialog';
import { shallow } from 'zustand/shallow';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
export const ReleaseVersionButton = function DeployVersionButton({
appId,
appName,
fetchApp,
onVersionRelease,
saveEditingVersion,
}) {
export const ReleaseVersionButton = function DeployVersionButton({ appId, appName, fetchApp, onVersionRelease }) {
const [isReleasing, setIsReleasing] = useState(false);
const { isVersionReleased, editingVersion } = useAppVersionStore(
(state) => ({

View file

@ -284,7 +284,7 @@ class ViewerComponent extends React.Component {
loadApplicationBySlug = (slug) => {
appService
.getAppBySlug(slug)
.fetchAppBySlug(slug)
.then((data) => {
this.setStateForApp(data, true);
this.setStateForContainer(data);
@ -302,7 +302,7 @@ class ViewerComponent extends React.Component {
loadApplicationByVersion = (appId, versionId) => {
appService
.getAppByVersion(appId, versionId)
.fetchAppByVersion(appId, versionId)
.then((data) => {
this.setStateForApp(data);
this.setStateForContainer(data, versionId);

View file

@ -1382,6 +1382,7 @@ const updateNewComponents = (pageId, appDefinition, newComponents, updateAppDefi
if (!isCut) {
opts.cloningComponent = componentMap;
}
updateAppDefinition(newAppDefinition, opts);
};

View file

@ -14,8 +14,11 @@ export const appService = {
changeIcon,
deleteApp,
getApp,
fetchApp,
getAppBySlug,
fetchAppBySlug,
getAppByVersion,
fetchAppByVersion,
saveApp,
getAppUsers,
createAppUser,
@ -124,6 +127,14 @@ function getApp(id, accessType) {
);
}
// v2 api for fetching app
function fetchApp(id, accessType) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/v2/apps/${id}${accessType ? `?access_type=${accessType}` : ''}`, requestOptions).then(
handleResponse
);
}
function deleteApp(id) {
const requestOptions = { method: 'DELETE', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/apps/${id}`, requestOptions).then(handleResponse);
@ -134,10 +145,19 @@ function getAppBySlug(slug) {
return fetch(`${config.apiUrl}/apps/slugs/${slug}`, requestOptions).then(handleResponse);
}
function fetchAppBySlug(slug) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/v2/apps/slugs/${slug}`, requestOptions).then(handleResponse);
}
function getAppByVersion(appId, versionId) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
}
function fetchAppByVersion(appId, versionId) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
}
function saveApp(id, attributes) {
const requestOptions = {

View file

@ -4,6 +4,7 @@ import { authHeader, handleResponse } from '@/_helpers';
export const appVersionService = {
getAll,
getOne,
getAppVersionData,
create,
del,
save,
@ -23,6 +24,10 @@ function getOne(appId, versionId) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
}
function getAppVersionData(appId, versionId) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
}
function create(appId, versionName, versionFromId) {
const body = {
@ -78,10 +83,6 @@ function autoSaveApp(appId, versionId, diff, type, pageId, operation, isUserSwit
global_settings: {
update: { ...diff },
},
events: {
update: diff,
create: diff,
},
};
const body = !type
@ -121,7 +122,7 @@ function saveAppVersionEventHandlers(appId, versionId, events, updateType = 'upd
function createAppVersionEventHandler(appId, versionId, event) {
const body = {
event,
...event,
};
const requestOptions = {

View file

@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React from 'react';
import { Link } from 'react-router-dom';
import useRouter from '@/_hooks/use-router';
import { ToolTip } from '@/_components/ToolTip';

View file

@ -28,16 +28,11 @@ import { EntityManager } from 'typeorm';
import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor';
import { AppDecorator } from 'src/decorators/app.decorator';
import { PageService } from '@services/page.service';
import { EventsService } from '@services/events_handler.service';
@Controller('apps')
export class AppsController {
constructor(
private appsService: AppsService,
private foldersService: FoldersService,
private pageService: PageService,
private eventsService: EventsService,
private appsAbilityFactory: AppsAbilityFactory
) {}
@ -84,17 +79,11 @@ export class AppsController {
}
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) : [];
const eventsForVersion = app.editingVersion
? await this.eventsService.findEventsForVersion(app.editingVersion.id)
: [];
// serialize queries
for (const query of dataQueriesForVersion) {
const decamelizedQuery = decamelizeKeys(query);
@ -104,8 +93,6 @@ export class AppsController {
response['data_queries'] = seralizedQueries;
response['definition'] = app.editingVersion?.definition;
response['pages'] = pagesForVersion;
response['events'] = eventsForVersion;
//! if editing version exists, camelize the definition
if (app.editingVersion && app.editingVersion.definition) {
@ -136,9 +123,6 @@ export class AppsController {
? await this.appsService.findVersion(app.currentVersionId)
: await this.appsService.findVersion(app.editingVersion?.id);
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : [];
const eventsForVersion = app.editingVersion ? await this.eventsService.findEventsForVersion(versionToLoad.id) : [];
// serialize
return {
current_version_id: app['currentVersionId'],
@ -148,11 +132,6 @@ export class AppsController {
is_maintenance_on: app.isMaintenanceOn,
name: app.name,
slug: app.slug,
events: eventsForVersion,
pages: pagesForVersion,
homePageId: versionToLoad.homePageId,
globalSettings: versionToLoad.globalSettings,
showViewerNavigation: versionToLoad.showViewerNavigation,
};
}
@ -308,25 +287,7 @@ export class AppsController {
);
}
const pagesForVersion = await this.pageService.findPagesForVersion(versionId);
const eventsForVersion = await this.eventsService.findEventsForVersion(versionId);
const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion));
delete appCurrentEditingVersion['app'];
const appData = {
...app,
};
delete appData['editingVersion'];
return {
...appData,
editing_version: camelizeKeys(appCurrentEditingVersion),
pages: pagesForVersion,
events: eventsForVersion,
};
return { ...appVersion, data_queries: appVersion.dataQueries };
}
@UseGuards(JwtAuthGuard)

View file

@ -20,8 +20,8 @@ import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.fact
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 { CreatePageDto, DeletePageDto } from '@dto/pages.dto';
import { CreateComponentDto, DeleteComponentDto, UpdateComponentDto, LayoutUpdateDto } from '@dto/component.dto';
import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor';
import { AppDecorator } from 'src/decorators/app.decorator';
@ -30,6 +30,7 @@ import { ComponentsService } from '@services/components.service';
import { PageService } from '@services/page.service';
import { EventsService } from '@services/events_handler.service';
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
import { CreateEventHandlerDto, UpdateEventHandlerDto } from '@dto/event-handler.dto';
@Controller({
path: 'apps',
@ -40,6 +41,7 @@ export class AppsControllerV2 {
private appsService: AppsService,
private componentsService: ComponentsService,
private pageService: PageService,
private eventsService: EventsService,
private eventService: EventsService,
private appsAbilityFactory: AppsAbilityFactory
) {}
@ -73,6 +75,9 @@ export class AppsControllerV2 {
: [];
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(app.editingVersion.id) : [];
const eventsForVersion = app.editingVersion
? await this.eventsService.findEventsForVersion(app.editingVersion.id)
: [];
// serialize queries
for (const query of dataQueriesForVersion) {
@ -84,6 +89,7 @@ export class AppsControllerV2 {
response['data_queries'] = seralizedQueries;
response['definition'] = app.editingVersion?.definition;
response['pages'] = pagesForVersion;
response['events'] = eventsForVersion;
//! if editing version exists, camelize the definition
if (app.editingVersion && app.editingVersion.definition) {
@ -95,6 +101,84 @@ export class AppsControllerV2 {
return response;
}
async appFromSlug(@User() user, @AppDecorator() app: App) {
if (user) {
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('viewApp', app)) {
throw new ForbiddenException(
JSON.stringify({
organizationId: app.organizationId,
})
);
}
}
const versionToLoad = app.currentVersionId
? await this.appsService.findVersion(app.currentVersionId)
: await this.appsService.findVersion(app.editingVersion?.id);
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : [];
const eventsForVersion = app.editingVersion ? await this.eventsService.findEventsForVersion(versionToLoad.id) : [];
// serialize
return {
current_version_id: app['currentVersionId'],
data_queries: versionToLoad?.dataQueries,
definition: versionToLoad?.definition,
is_public: app.isPublic,
is_maintenance_on: app.isMaintenanceOn,
name: app.name,
slug: app.slug,
events: eventsForVersion,
pages: pagesForVersion,
homePageId: versionToLoad.homePageId,
globalSettings: versionToLoad.globalSettings,
showViewerNavigation: versionToLoad.showViewerNavigation,
};
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Get(':id/versions/:versionId')
async version(@User() user, @Param('id') id, @Param('versionId') versionId) {
const appVersion = await this.appsService.findVersion(versionId);
const app = appVersion.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('fetchVersions', app)) {
throw new ForbiddenException(
JSON.stringify({
organizationId: app.organizationId,
})
);
}
const pagesForVersion = await this.pageService.findPagesForVersion(versionId);
const eventsForVersion = await this.eventsService.findEventsForVersion(versionId);
const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion));
delete appCurrentEditingVersion['app'];
const appData = {
...app,
};
delete appData['editingVersion'];
return {
...appData,
editing_version: camelizeKeys(appCurrentEditingVersion),
pages: pagesForVersion,
events: eventsForVersion,
};
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId')
@ -118,6 +202,7 @@ export class AppsControllerV2 {
return await this.appsService.updateAppVersion(version, appVersionUpdateDto);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/global_settings')
@ -150,7 +235,7 @@ export class AppsControllerV2 {
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
@Body() createComponentDto: CreateComponentDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
@ -164,7 +249,7 @@ export class AppsControllerV2 {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.create(versionEditDto.diff, versionEditDto.pageId, versionId);
await this.componentsService.create(createComponentDto.diff, createComponentDto.pageId, versionId);
}
@UseGuards(JwtAuthGuard)
@ -174,7 +259,7 @@ export class AppsControllerV2 {
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
@Body() updateComponentDto: UpdateComponentDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
@ -188,7 +273,7 @@ export class AppsControllerV2 {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.update(versionEditDto.diff, versionId);
await this.componentsService.update(updateComponentDto.diff, versionId);
}
@UseGuards(JwtAuthGuard)
@ -198,7 +283,7 @@ export class AppsControllerV2 {
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
@Body() deleteComponentDto: DeleteComponentDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
@ -212,7 +297,7 @@ export class AppsControllerV2 {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.delete(versionEditDto.diff, versionId);
await this.componentsService.delete(deleteComponentDto.diff, versionId);
}
@UseGuards(JwtAuthGuard)
@ -222,7 +307,7 @@ export class AppsControllerV2 {
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() versionEditDto: VersionEditDto
@Body() updateComponentLayout: LayoutUpdateDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
@ -236,7 +321,7 @@ export class AppsControllerV2 {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.componentsService.componentLayoutChange(versionEditDto.diff, versionId);
await this.componentsService.componentLayoutChange(updateComponentLayout.diff, versionId);
}
// pages api
@ -282,11 +367,50 @@ export class AppsControllerV2 {
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/pages')
async updatePages(
async updatePages(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() 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(updatePageDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/pages')
async deletePage(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() deletePageDto: DeletePageDto) {
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.deletePage(deletePageDto.pageId, versionId);
}
// events api
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Post(':id/versions/:versionId/events')
async createEvent(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() updatePageDto: Partial<UpdatePageDto>
@Body() createEventHandlerDto: CreateEventHandlerDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
@ -300,57 +424,17 @@ export class AppsControllerV2 {
throw new ForbiddenException('You do not have permissions to perform this action');
}
await this.pageService.updatePage({ pageId: updatePageDto.pageId, diff: updatePageDto.diff }, versionId);
}
@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);
}
}
// events api
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Post(':id/versions/:versionId/events')
async createEvent(@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 { event } = body;
return this.eventService.createEvent(event, versionId);
return this.eventService.createEvent(createEventHandlerDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/events')
async updateEvents(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() body) {
async updateEvents(
@User() user,
@Param('id') id,
@Param('versionId') versionId,
@Body() updateEventHandlerDto: UpdateEventHandlerDto
) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
@ -363,7 +447,9 @@ export class AppsControllerV2 {
throw new ForbiddenException('You do not have permissions to perform this action');
}
return await this.eventService.updateEvent(body?.events, body?.updateType, versionId);
const { events, updateType } = updateEventHandlerDto;
return await this.eventService.updateEvent(events, updateType, versionId);
}
@UseGuards(JwtAuthGuard)

View file

@ -1,4 +1,4 @@
import { IsBoolean, IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator';
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator';
import { Transform } from 'class-transformer';
import { sanitizeInput } from '../helpers/utils.helper';
@ -17,7 +17,7 @@ export class AppVersionUpdateDto {
@IsOptional()
showViewerNavigation: boolean;
@IsString()
@IsUUID()
@IsOptional()
homePageId: string;

View file

@ -0,0 +1,102 @@
import { Type } from 'class-transformer';
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, IsUUID } from 'class-validator';
export class ComponentLayoutDto {
@IsNumber()
@IsOptional()
top?: number;
@IsNumber()
@IsOptional()
left?: number;
@IsNumber()
@IsOptional()
width?: number;
@IsNumber()
@IsOptional()
height?: number;
}
export class LayoutData {
@IsObject()
@IsOptional()
desktop?: ComponentLayoutDto;
@IsObject()
@IsOptional()
mobile?: ComponentLayoutDto;
}
class ComponentDto {
@IsString()
name: string;
@IsObject()
properties: Record<string, any>;
@IsObject()
styles: Record<string, any>;
@IsObject()
validation: Record<string, any>;
@IsString()
type: string;
@IsObject()
others: Record<string, any>;
@IsOptional()
@Type(() => ComponentLayoutDto)
layouts: ComponentLayoutDto;
@IsOptional()
parent: string;
}
export class CreateComponentDto {
@IsBoolean()
is_user_switched_version: boolean;
@IsUUID()
pageId: string;
@IsObject()
diff: Record<string, ComponentDto>;
}
export class UpdateComponentDto {
@IsBoolean()
is_user_switched_version: boolean;
@IsUUID()
pageId: string;
@IsObject()
diff: Record<string, ComponentDto>;
}
export class DeleteComponentDto {
@IsBoolean()
is_user_switched_version: boolean;
@IsUUID()
pageId: string;
@IsArray()
diff: string[];
}
export class LayoutUpdateDto {
@IsBoolean()
is_user_switched_version: boolean;
@IsUUID()
pageId: string;
@IsObject()
@IsNotEmpty()
diff: Record<string, { layouts: LayoutData }>;
}

View file

@ -0,0 +1,44 @@
import { IsArray, IsIn, IsNumber, IsObject, IsString, IsUUID, ValidateNested } from 'class-validator';
import { Target } from 'src/entities/event_handler.entity';
export class CreateEventHandlerDto {
@IsObject()
event: any;
@IsString()
eventType: Target;
@IsString()
attachedTo: string;
@IsNumber()
index: number;
}
class UpdateEventDiff {
@IsString()
name: string;
@IsNumber()
index: number;
@IsObject()
@ValidateNested()
event: any;
}
export class UpdateEvent {
@IsUUID()
event_id: string;
@IsObject()
diff: UpdateEventDiff;
}
export class UpdateEventHandlerDto {
@IsArray()
events: UpdateEvent[];
@IsIn(['update', 'reorder'])
updateType: 'update' | 'reorder';
}

View file

@ -1,17 +1,35 @@
import { IsNumber, IsString, IsUUID } from 'class-validator';
import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator';
export class CreatePageDto {
@IsUUID()
@IsNotEmpty()
id: string;
@IsString()
@IsNotEmpty()
@MaxLength(32)
name: string;
@IsString()
@IsNotEmpty()
@MaxLength(50)
handle: string;
@IsNumber()
@IsNotEmpty()
index: number;
@IsOptional()
disabled: boolean;
@IsOptional()
hidden: boolean;
}
export class DeletePageDto {
@IsUUID()
@IsNotEmpty()
pageId: string;
}
export class UpdatePageDto {

View file

@ -114,6 +114,7 @@ export class AppEnvironmentService {
order: {
createdAt: 'DESC',
},
select: ['id', 'name', 'appId'],
});
});
}

View file

@ -28,6 +28,7 @@ export class ComponentsService {
});
const newComponents = this.transformComponentData(componentDiff);
const componentLayouts = [];
newComponents.forEach((component) => {

View file

@ -1,15 +1,14 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm';
import { Component } from 'src/entities/component.entity';
import { EventHandler } from 'src/entities/event_handler.entity';
import { dbTransactionWrap, dbTransactionForAppVersionAssociationsUpdate } from 'src/helpers/utils.helper';
import { CreateEventHandlerDto, UpdateEvent } from '@dto/event-handler.dto';
@Injectable()
export class EventsService {
constructor(
@InjectRepository(Component)
@InjectRepository(EventHandler)
private eventsRepository: Repository<EventHandler>
) {}
@ -23,11 +22,8 @@ export class EventsService {
}
async findAllEventsWithSourceId(sourceId: string): Promise<EventHandler[]> {
return dbTransactionWrap(async (manager: EntityManager) => {
const allEvents = await manager.find(EventHandler, {
where: { sourceId },
});
return allEvents;
return this.eventsRepository.find({
where: { sourceId },
});
}
@ -41,31 +37,38 @@ export class EventsService {
});
}
async createEvent(eventObj, versionId) {
if (Object.keys(eventObj).length === 0) {
return new BadRequestException('No event found');
async createEvent(eventHandler: CreateEventHandlerDto, versionId) {
if (!eventHandler.attachedTo) {
throw new BadRequestException('No attachedTo found');
}
const newEvent = {
name: eventObj.event.eventId,
sourceId: eventObj.attachedTo,
target: eventObj.eventType,
event: eventObj.event,
appVersionId: versionId,
index: eventObj.index,
};
if (!eventHandler.eventType) {
throw new BadRequestException('No eventType found');
}
if (!eventHandler.event) {
throw new BadRequestException('No event found');
}
return await dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
const newEvent = new EventHandler();
newEvent.name = eventHandler.event.eventId;
newEvent.sourceId = eventHandler.attachedTo;
newEvent.target = eventHandler.eventType;
newEvent.event = eventHandler.event;
newEvent.index = eventHandler.index;
newEvent.appVersionId = versionId;
const event = await manager.save(EventHandler, newEvent);
return event;
}, versionId);
}
async updateEvent(events: [], updateType: 'update' | 'reorder', appVersionId: string) {
async updateEvent(events: UpdateEvent[], updateType: 'update' | 'reorder', appVersionId: string) {
return await dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
return await Promise.all(
events.map(async (event) => {
const { event_id, diff } = event as any;
const { event_id, diff } = event;
const eventDiff = diff?.event;
const eventToUpdate = await manager.findOne(EventHandler, {
@ -78,12 +81,13 @@ export class EventsService {
const updatedEvent = {
...eventToUpdate,
event: {
...eventToUpdate.event,
...eventDiff,
},
};
if (updateType === 'update') {
updatedEvent.name = eventDiff?.eventId;
updatedEvent.event = eventDiff;
}
if (updateType === 'reorder') {
updatedEvent.index = diff.index;
}
@ -96,17 +100,13 @@ export class EventsService {
async updateEventsOrderOnDelete(sourceId: string, deletedIndex: number) {
const allEvents = await this.findAllEventsWithSourceId(sourceId);
const eventsToUpdate = allEvents.filter((event) => event.index > deletedIndex);
return await dbTransactionWrap(async (manager: EntityManager) => {
return await Promise.all(
eventsToUpdate.map(async (event) => {
const updatedEvent = {
...event,
index: event.index - 1,
};
return await manager.save(EventHandler, updatedEvent);
return await manager.update(EventHandler, { id: event.id }, { index: event.index - 1 });
})
);
});
@ -118,6 +118,9 @@ export class EventsService {
where: { id: eventId },
});
const sourceId = event.sourceId;
const deletedIndex = event.index;
if (!event) {
return new BadRequestException('No event found');
}
@ -127,7 +130,7 @@ export class EventsService {
if (!deleteResponse?.affected) {
throw new NotFoundException();
}
await this.updateEventsOrderOnDelete(event.sourceId, event.index);
await this.updateEventsOrderOnDelete(sourceId, deletedIndex);
return deleteResponse;
}, appVersionId);
}

View file

@ -110,7 +110,14 @@ export class PageService {
event.event = eventDefinition;
const clonedEvent = { ...event, id: undefined, sourceId: clonePageId };
const clonedEvent = new EventHandler();
clonedEvent.event = event.event;
clonedEvent.index = event.index;
clonedEvent.name = event.name;
clonedEvent.sourceId = clonePageId;
clonedEvent.target = event.target;
clonedEvent.appVersionId = event.appVersionId;
await manager.save(EventHandler, clonedEvent);
})
);
@ -139,11 +146,15 @@ export class PageService {
}
event.event = eventDefinition;
return {
...event,
id: undefined,
sourceId: newComponent.id,
};
const clonedEvent = new EventHandler();
clonedEvent.event = event.event;
clonedEvent.index = event.index;
clonedEvent.name = event.name;
clonedEvent.sourceId = newComponent.id;
clonedEvent.target = event.target;
clonedEvent.appVersionId = event.appVersionId;
return clonedEvent;
});
await manager.save(Layout, clonedLayouts);