diff --git a/frontend/src/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx index a130a36ecf..251e6ec104 100644 --- a/frontend/src/AppLoader/AppLoader.jsx +++ b/frontend/src/AppLoader/AppLoader.jsx @@ -23,7 +23,7 @@ const AppLoaderComponent = (props) => { const loadAppDetails = () => { appService - .getApp(appId, 'edit') + .fetchApp(appId, 'edit') .then((data) => { setShouldLoadApp(true); updateState({ diff --git a/frontend/src/Editor/AppVersionsManager/List.jsx b/frontend/src/Editor/AppVersionsManager/List.jsx index a8507f64e3..40dbb10aed 100644 --- a/frontend/src/Editor/AppVersionsManager/List.jsx +++ b/frontend/src/Editor/AppVersionsManager/List.jsx @@ -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, diff --git a/frontend/src/Editor/CodeBuilder/Elements/ClientServerSwitch.jsx b/frontend/src/Editor/CodeBuilder/Elements/ClientServerSwitch.jsx index 0490ac5918..cd80287291 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/ClientServerSwitch.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/ClientServerSwitch.jsx @@ -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) => { diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index 4213a9d4c2..e5b7713060 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -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) => { { 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 ( {renderDraggable((provided, snapshot) => { diff --git a/frontend/src/Editor/QueryManager/Components/QueryManagerBody.jsx b/frontend/src/Editor/QueryManager/Components/QueryManagerBody.jsx index 0d3467d36a..6bfbd12205 100644 --- a/frontend/src/Editor/QueryManager/Components/QueryManagerBody.jsx +++ b/frontend/src/Editor/QueryManager/Components/QueryManagerBody.jsx @@ -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'; diff --git a/frontend/src/Editor/ReleaseVersionButton.jsx b/frontend/src/Editor/ReleaseVersionButton.jsx index cd6a68bd64..26103802b6 100644 --- a/frontend/src/Editor/ReleaseVersionButton.jsx +++ b/frontend/src/Editor/ReleaseVersionButton.jsx @@ -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) => ({ diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index a5f4560c0a..e87724e976 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -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); diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 7ec745675c..3e47bc26ba 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -1382,6 +1382,7 @@ const updateNewComponents = (pageId, appDefinition, newComponents, updateAppDefi if (!isCut) { opts.cloningComponent = componentMap; } + updateAppDefinition(newAppDefinition, opts); }; diff --git a/frontend/src/_services/app.service.js b/frontend/src/_services/app.service.js index 0329954eac..0c50d0d690 100644 --- a/frontend/src/_services/app.service.js +++ b/frontend/src/_services/app.service.js @@ -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 = { diff --git a/frontend/src/_services/appVersion.service.js b/frontend/src/_services/appVersion.service.js index 90b13eab0e..f55fb9a115 100644 --- a/frontend/src/_services/appVersion.service.js +++ b/frontend/src/_services/appVersion.service.js @@ -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 = { diff --git a/frontend/src/_ui/Layout/index.jsx b/frontend/src/_ui/Layout/index.jsx index 4bbdc5935e..82909391b3 100644 --- a/frontend/src/_ui/Layout/index.jsx +++ b/frontend/src/_ui/Layout/index.jsx @@ -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'; diff --git a/server/src/controllers/apps.controller.ts b/server/src/controllers/apps.controller.ts index cbb426dde2..45873792d0 100644 --- a/server/src/controllers/apps.controller.ts +++ b/server/src/controllers/apps.controller.ts @@ -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) diff --git a/server/src/controllers/apps.controller.v2.ts b/server/src/controllers/apps.controller.v2.ts index ab2d72511a..7c972581ef 100644 --- a/server/src/controllers/apps.controller.v2.ts +++ b/server/src/controllers/apps.controller.v2.ts @@ -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 + @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) diff --git a/server/src/dto/app-version-update.dto.ts b/server/src/dto/app-version-update.dto.ts index 00e52d4f6c..53d79b387a 100644 --- a/server/src/dto/app-version-update.dto.ts +++ b/server/src/dto/app-version-update.dto.ts @@ -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; diff --git a/server/src/dto/component.dto.ts b/server/src/dto/component.dto.ts new file mode 100644 index 0000000000..a1bbf9a994 --- /dev/null +++ b/server/src/dto/component.dto.ts @@ -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; + + @IsObject() + styles: Record; + + @IsObject() + validation: Record; + + @IsString() + type: string; + + @IsObject() + others: Record; + + @IsOptional() + @Type(() => ComponentLayoutDto) + layouts: ComponentLayoutDto; + + @IsOptional() + parent: string; +} + +export class CreateComponentDto { + @IsBoolean() + is_user_switched_version: boolean; + + @IsUUID() + pageId: string; + + @IsObject() + diff: Record; +} + +export class UpdateComponentDto { + @IsBoolean() + is_user_switched_version: boolean; + + @IsUUID() + pageId: string; + + @IsObject() + diff: Record; +} + +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; +} diff --git a/server/src/dto/event-handler.dto.ts b/server/src/dto/event-handler.dto.ts new file mode 100644 index 0000000000..80623de736 --- /dev/null +++ b/server/src/dto/event-handler.dto.ts @@ -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'; +} diff --git a/server/src/dto/pages.dto.ts b/server/src/dto/pages.dto.ts index 9113990d57..875585b3ce 100644 --- a/server/src/dto/pages.dto.ts +++ b/server/src/dto/pages.dto.ts @@ -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 { diff --git a/server/src/services/app_environments.service.ts b/server/src/services/app_environments.service.ts index 1223ec1f93..461f486e1c 100644 --- a/server/src/services/app_environments.service.ts +++ b/server/src/services/app_environments.service.ts @@ -114,6 +114,7 @@ export class AppEnvironmentService { order: { createdAt: 'DESC', }, + select: ['id', 'name', 'appId'], }); }); } diff --git a/server/src/services/components.service.ts b/server/src/services/components.service.ts index 240531059d..52edd6b115 100644 --- a/server/src/services/components.service.ts +++ b/server/src/services/components.service.ts @@ -28,6 +28,7 @@ export class ComponentsService { }); const newComponents = this.transformComponentData(componentDiff); + const componentLayouts = []; newComponents.forEach((component) => { diff --git a/server/src/services/events_handler.service.ts b/server/src/services/events_handler.service.ts index 08314e40f9..be4216fbeb 100644 --- a/server/src/services/events_handler.service.ts +++ b/server/src/services/events_handler.service.ts @@ -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 ) {} @@ -23,11 +22,8 @@ export class EventsService { } async findAllEventsWithSourceId(sourceId: string): Promise { - 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); } diff --git a/server/src/services/page.service.ts b/server/src/services/page.service.ts index c4689fbfa1..e0b4d125a9 100644 --- a/server/src/services/page.service.ts +++ b/server/src/services/page.service.ts @@ -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);