diff --git a/apps/extension/src/contentscript/helpers.ts b/apps/extension/src/contentscript/helpers.ts index 200dc901..a39f7f98 100644 --- a/apps/extension/src/contentscript/helpers.ts +++ b/apps/extension/src/contentscript/helpers.ts @@ -1,4 +1,4 @@ -import { Mutation } from '@mweb/engine' +import { MutationDto } from '@mweb/engine' /** * Simple object check. @@ -27,7 +27,7 @@ export const compareDeep = (a: unknown, b: unknown) => JSON.stringify(a) === JSO * @param m1 * @param m2 */ -export const compareMutations = (m1: Mutation, m2: Mutation): boolean => +export const compareMutations = (m1: MutationDto, m2: MutationDto): boolean => !( m1.id !== m2.id || !compareDeep(m1.targets, m2.targets) || diff --git a/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx b/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx index bc74fb04..10362c57 100644 --- a/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx +++ b/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx @@ -1,4 +1,4 @@ -import { AppMetadata, Document, useAppDocuments } from '@mweb/engine' +import { ApplicationDto, DocumentDto, useAppDocuments } from '@mweb/engine' import React from 'react' import styled from 'styled-components' import { Image } from './image' @@ -182,7 +182,7 @@ const CheckedIcon = () => ( export interface ISimpleApplicationCardProps { src: string - metadata: AppMetadata['metadata'] + metadata: ApplicationDto['metadata'] disabled: boolean isChecked: boolean onChange: (isChecked: boolean) => void @@ -193,19 +193,19 @@ export interface ISimpleApplicationCardProps { export interface IApplicationCardWithDocsProps { src: string - metadata: AppMetadata['metadata'] + metadata: ApplicationDto['metadata'] disabled: boolean docsIds: AppInMutation['documentId'][] onDocCheckboxChange: (docId: string | null, isChecked: boolean) => void - onOpenDocumentsModal: (docs: Document[]) => void + onOpenDocumentsModal: (docs: DocumentDto[]) => void } interface IApplicationCard extends ISimpleApplicationCardProps, Omit { hasDocuments: boolean - usingDocs: (Document | null)[] - allDocs: Document[] + usingDocs: (DocumentDto | null)[] + allDocs: DocumentDto[] } const ApplicationCard: React.FC = ({ @@ -289,7 +289,7 @@ export const SimpleApplicationCard: React.FC = (pro export const ApplicationCardWithDocs: React.FC = (props) => { const { src, docsIds } = props const { documents, isLoading } = useAppDocuments(src) - const usingDocs: (Document | null)[] = documents?.filter((doc) => docsIds.includes(doc.id)) + const usingDocs: (DocumentDto | null)[] = documents?.filter((doc) => docsIds.includes(doc.id)) if (docsIds.includes(null)) usingDocs.unshift(null) return isLoading ? ( diff --git a/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx b/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx index 18e63b10..642584ec 100644 --- a/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx +++ b/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx @@ -1,4 +1,4 @@ -import { AppMetadata } from '@mweb/engine' +import { ApplicationDto } from '@mweb/engine' import React from 'react' import styled from 'styled-components' import { Image } from './image' @@ -140,7 +140,7 @@ export interface Props { metadata: DocumentMetadata | null onChange: () => void disabled: boolean - appMetadata: AppMetadata['metadata'] + appMetadata: ApplicationDto['metadata'] } export const DocumentCard: React.FC = ({ diff --git a/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx b/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx index 5e77704f..b5893c24 100644 --- a/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx +++ b/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx @@ -1,4 +1,4 @@ -import { Document } from '@mweb/engine' +import { DocumentDto } from '@mweb/engine' import React, { FC, useState } from 'react' import styled from 'styled-components' import { SimpleApplicationCard } from './application-card' @@ -142,7 +142,7 @@ const CloseIcon = () => ( ) export interface Props { - docs: Document[] | null + docs: DocumentDto[] | null chosenDocumentsIds: (string | null)[] setDocumentsIds: (ids: (string | null)[]) => void onClose: () => void diff --git a/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx b/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx index aadbc4aa..3bf12e51 100644 --- a/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx +++ b/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx @@ -1,7 +1,7 @@ import { - AppMetadata, - Document, - Mutation, + ApplicationDto, + DocumentDto, + MutationDto, useCreateMutation, useEditMutation, useMutableWeb, @@ -147,9 +147,15 @@ const CloseIcon = () => ( ) -const createEmptyMutation = (accountId: string): Mutation => - Mutation.create({ - id: `${accountId}/mutation/Untitled-${generateRandomHex(6)}`, +// ToDo: use MutationCreateDto +const createEmptyMutation = (accountId: string): MutationDto => { + const localId = `Untitled-${generateRandomHex(6)}` + return { + id: `${accountId}/mutation/${localId}`, + authorId: accountId, + blockNumber: 0, + timestamp: 0, + localId: localId, apps: [], metadata: { name: '', @@ -161,11 +167,12 @@ const createEmptyMutation = (accountId: string): Mutation => if: { id: { in: [window.location.hostname] } }, }, ], - }) + } +} export interface Props { - apps: AppMetadata[] - baseMutation: Mutation | null + apps: ApplicationDto[] + baseMutation: MutationDto | null onClose: () => void } @@ -219,7 +226,7 @@ export const MutationEditorModal: FC = ({ baseMutation, apps, onClose }) const { mutations } = useMutableWeb() const [isModified, setIsModified] = useState(true) const [appIdToOpenDocsModal, setAppIdToOpenDocsModal] = useState(null) - const [docsForModal, setDocsForModal] = useState(null) + const [docsForModal, setDocsForModal] = useState(null) // Close modal with escape key useEscape(onClose) @@ -392,7 +399,7 @@ export const MutationEditorModal: FC = ({ baseMutation, apps, onClose }) setMode(itemId as MutationModalMode) } - const handleOpenDocumentsModal = (appId: string, docs: Document[]) => { + const handleOpenDocumentsModal = (appId: string, docs: DocumentDto[]) => { setAppIdToOpenDocsModal(appId) setDocsForModal(docs) } @@ -442,7 +449,7 @@ export const MutationEditorModal: FC = ({ baseMutation, apps, onClose }) docsIds={editingMutation.apps .filter((_app) => _app.appId === app.id) .map((_app) => _app.documentId)} - onOpenDocumentsModal={(docs: Document[]) => handleOpenDocumentsModal(app.id, docs)} + onOpenDocumentsModal={(docs: DocumentDto[]) => handleOpenDocumentsModal(app.id, docs)} onDocCheckboxChange={(docId: string | null, isChecked: boolean) => handleDocCheckboxChange(docId, app.id, isChecked) } diff --git a/libs/engine/src/app/components/context-manager.tsx b/libs/engine/src/app/components/context-manager.tsx index 9c96567b..885d8056 100644 --- a/libs/engine/src/app/components/context-manager.tsx +++ b/libs/engine/src/app/components/context-manager.tsx @@ -8,9 +8,9 @@ import { ShadowDomWrapper } from '../components/shadow-dom-wrapper' import { ContextTree } from '@mweb/react' import { useContextApps } from '../contexts/mutable-web-context/use-context-apps' import { useAppControllers } from '../contexts/mutable-web-context/use-app-controllers' -import { AppId, AppMetadata } from '../services/application/application.entity' +import { AppId } from '../services/application/application.entity' +import { ApplicationDto } from '../services/application/dtos/application.dto' import { - BosUserLink, BosUserLinkWithInstance, ControllerLink, UserLinkId, @@ -27,7 +27,7 @@ import { ModalProps } from '../contexts/modal-context/modal-context' import { Portal } from '../contexts/engine-context/engine-context' import { Target } from '../services/target/target.entity' import { filterAndDiscriminate } from '../common/filter-and-discriminate' -import { Document, DocumentId, DocumentMetadata } from '../services/document/document.entity' +import { DocumentId, DocumentMetadata } from '../services/document/document.entity' import { ApplicationService } from '../services/application/application.service' import { DocumentDto } from '../services/document/dtos/document.dto' @@ -240,7 +240,7 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE memoize( (appInstanceId: string) => async ( - appDocId: DocumentId, + appDocId: DocumentId, // ToDo: remove appDocMeta: DocumentMetadata, ctx: TransferableContext, dataByAccount: LinkedDataByAccountDto @@ -251,11 +251,16 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE ) if (!appInstance) throw new Error('The app is not active') - const document = Document.create({ + // ToDo: replace with DocumentCreateDto + const document: DocumentDto = { id: appDocId, + authorId: appDocId.split('/')[0], + localId: appDocId.split('/')[2], + blockNumber: 0, + timestamp: 0, metadata: appDocMeta, openWith: [appInstance.appId], - }) + } const { mutation } = await engine.documentService.createDocumentWithData( selectedMutation.id, @@ -358,7 +363,7 @@ const InsPointHandler: FC<{ transferableContext: TransferableContext allUserLinks: BosUserLinkWithInstance[] components: Portal[] - apps: AppMetadata[] + apps: ApplicationDto[] isEditMode: boolean onContextQuery: (target: Target) => TransferableContext | null onCreateUserLink: (appId: AppId) => Promise diff --git a/libs/engine/src/app/contexts/mutable-web-context/mutable-web-context.tsx b/libs/engine/src/app/contexts/mutable-web-context/mutable-web-context.tsx index c463115f..f3871e73 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/mutable-web-context.tsx +++ b/libs/engine/src/app/contexts/mutable-web-context/mutable-web-context.tsx @@ -1,18 +1,20 @@ import { Engine } from '../../../engine' import { createContext } from 'react' -import { AppMetadata, AppInstanceWithSettings } from '../../services/application/application.entity' -import { Mutation, MutationWithSettings } from '../../services/mutation/mutation.entity' +import { AppInstanceWithSettings } from '../../services/application/application.entity' +import { ApplicationDto } from '../../services/application/dtos/application.dto' +import { MutationWithSettings } from '../../services/mutation/mutation.entity' +import { MutationDto } from '../../services/mutation/dtos/mutation.dto' import { NearConfig } from '../../../constants' export type MutableWebContextState = { config: NearConfig engine: Engine mutations: MutationWithSettings[] - allApps: AppMetadata[] + allApps: ApplicationDto[] mutationApps: AppInstanceWithSettings[] activeApps: AppInstanceWithSettings[] selectedMutation: MutationWithSettings | null - refreshMutation: (mutation: Mutation) => Promise + refreshMutation: (mutation: MutationDto) => Promise isLoading: boolean switchMutation: (mutationId: string | null) => void favoriteMutationId: string | null diff --git a/libs/engine/src/app/contexts/mutable-web-context/mutable-web-provider.tsx b/libs/engine/src/app/contexts/mutable-web-context/mutable-web-provider.tsx index f70db0ca..567f0f6a 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/mutable-web-provider.tsx +++ b/libs/engine/src/app/contexts/mutable-web-context/mutable-web-provider.tsx @@ -10,7 +10,7 @@ import { TargetService } from '../../services/target/target.service' import { mutationDisabled, mutationSwitched } from './notifications' import { getNearConfig } from '../../../constants' import { ModalContextState } from '../modal-context/modal-context' -import { Mutation } from '../../services/mutation/mutation.entity' +import { MutationDto } from '../../services/mutation/dtos/mutation.dto' import { ParserType, ParserConfig } from '@mweb/core' type Props = { @@ -159,7 +159,7 @@ const MutableWebProvider: FC = ({ config, defaultMutationId, modalApi, ch // Update last usage for selected mutation setMutations((prev) => prev.map((mut) => - mut.id === mutationId ? mut.copy({ settings: { ...mut.settings, lastUsage } }) : mut + mut.id === mutationId ? { ...mut, settings: { ...mut.settings, lastUsage } } : mut ) ) } @@ -169,7 +169,7 @@ const MutableWebProvider: FC = ({ config, defaultMutationId, modalApi, ch [selectedMutationId] ) - const refreshMutation = useCallback(async (mutation: Mutation) => { + const refreshMutation = useCallback(async (mutation: MutationDto) => { const mutationWithSettings = await engine.mutationService.populateMutationWithSettings(mutation) setMutations((prev) => prev.map((mut) => (mut.id === mutation.id ? mutationWithSettings : mut))) @@ -196,9 +196,7 @@ const MutableWebProvider: FC = ({ config, defaultMutationId, modalApi, ch setMutations((prev) => prev.map((mut) => - mut.id === mutationId - ? mut.copy({ settings: { ...mut.settings, lastUsage: null } }) - : mut + mut.id === mutationId ? { ...mut, settings: { ...mut.settings, lastUsage: null } } : mut ) ) } catch (err) { diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-app-documents.ts b/libs/engine/src/app/contexts/mutable-web-context/use-app-documents.ts index 0579b721..587a28f2 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-app-documents.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-app-documents.ts @@ -1,12 +1,12 @@ import { AppId } from '../../services/application/application.entity' -import { Document } from '../../services/document/document.entity' +import { DocumentDto } from '../../services/document/dtos/document.dto' import { useMutableWeb } from './use-mutable-web' import { useQueryArray } from '../../hooks/use-query-array' export const useAppDocuments = (appId: AppId) => { const { engine } = useMutableWeb() - const { data, isLoading, error } = useQueryArray({ + const { data, isLoading, error } = useQueryArray({ query: () => engine.documentService.getDocumentsByAppId(appId), deps: [engine, appId], }) diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-applications.ts b/libs/engine/src/app/contexts/mutable-web-context/use-applications.ts index 51476f12..f46f95c2 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-applications.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-applications.ts @@ -1,9 +1,9 @@ -import { AppMetadata } from '../../services/application/application.entity' +import { ApplicationDto } from '../../services/application/dtos/application.dto' import { Engine } from '../../../engine' import { useQueryArray } from '../../hooks/use-query-array' export const useApplications = (engine: Engine) => { - const { data, isLoading, error } = useQueryArray({ + const { data, isLoading, error } = useQueryArray({ query: () => engine.applicationService.getApplications(), deps: [engine], }) diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-create-mutation.ts b/libs/engine/src/app/contexts/mutable-web-context/use-create-mutation.ts index c888de02..b0de0948 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-create-mutation.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-create-mutation.ts @@ -1,7 +1,7 @@ import { useContext, useState } from 'react' import { MutableWebContext } from './mutable-web-context' -import { Mutation } from '../../services/mutation/mutation.entity' import { SaveMutationOptions } from '../../services/mutation/mutation.service' +import { MutationCreateDto } from '../../services/mutation/dtos/mutation-create.dto' export function useCreateMutation() { const { engine, setMutations } = useContext(MutableWebContext) @@ -9,7 +9,10 @@ export function useCreateMutation() { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) - const createMutation = async (creatingMutation: Mutation, options?: SaveMutationOptions) => { + const createMutation = async ( + creatingMutation: MutationCreateDto, + options?: SaveMutationOptions + ) => { try { setIsLoading(true) diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-edit-mutation.ts b/libs/engine/src/app/contexts/mutable-web-context/use-edit-mutation.ts index 95995113..f838b814 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-edit-mutation.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-edit-mutation.ts @@ -1,4 +1,4 @@ -import { Mutation } from '../../services/mutation/mutation.entity' +import { MutationDto } from '../../services/mutation/dtos/mutation.dto' import { useContext, useState } from 'react' import { MutableWebContext } from './mutable-web-context' import { SaveMutationOptions } from '../../services/mutation/mutation.service' @@ -9,7 +9,7 @@ export function useEditMutation() { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) - const editMutation = async (editingMutation: Mutation, options?: SaveMutationOptions) => { + const editMutation = async (editingMutation: MutationDto, options?: SaveMutationOptions) => { try { setIsLoading(true) diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-mutation-app.ts b/libs/engine/src/app/contexts/mutable-web-context/use-mutation-app.ts index 1f1a4c82..2e3b2a59 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-mutation-app.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-mutation-app.ts @@ -23,7 +23,7 @@ export function useMutationApp(appInstanceId: string) { setMutationApps((apps) => apps.map((app) => app.instanceId === appInstanceId - ? app.copy({ settings: { ...app.settings, isEnabled: true } }) + ? { ...app, settings: { ...app.settings, isEnabled: true } } : app ) ) @@ -55,7 +55,7 @@ export function useMutationApp(appInstanceId: string) { setMutationApps((apps) => apps.map((app) => app.instanceId === appInstanceId - ? app.copy({ settings: { ...app.settings, isEnabled: false } }) + ? { ...app, settings: { ...app.settings, isEnabled: false } } : app ) ) diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-mutation-apps.ts b/libs/engine/src/app/contexts/mutable-web-context/use-mutation-apps.ts index ab96ca72..e1678011 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-mutation-apps.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-mutation-apps.ts @@ -1,9 +1,9 @@ import { AppInstanceWithSettings } from '../../services/application/application.entity' -import { Mutation } from '../../services/mutation/mutation.entity' +import { MutationDto } from '../../services/mutation/dtos/mutation.dto' import { Engine } from '../../../engine' import { useQueryArray } from '../../hooks/use-query-array' -export const useMutationApps = (engine: Engine, mutation?: Mutation | null) => { +export const useMutationApps = (engine: Engine, mutation?: MutationDto | null) => { const { data, setData, isLoading, error } = useQueryArray({ query: async () => (mutation ? engine.applicationService.getAppsFromMutation(mutation) : []), deps: [engine, mutation], diff --git a/libs/engine/src/app/contexts/mutable-web-context/use-mutation-parsers.ts b/libs/engine/src/app/contexts/mutable-web-context/use-mutation-parsers.ts index aaeaa8a6..7410f92f 100644 --- a/libs/engine/src/app/contexts/mutable-web-context/use-mutation-parsers.ts +++ b/libs/engine/src/app/contexts/mutable-web-context/use-mutation-parsers.ts @@ -1,9 +1,9 @@ -import { AppMetadata } from '../../services/application/application.entity' +import { ApplicationDto } from '../../services/application/dtos/application.dto' import { ParserConfig } from '../../services/parser-config/parser-config.entity' import { Engine } from '../../../engine' import { useQueryArray } from '../../hooks/use-query-array' -export const useMutationParsers = (engine: Engine, apps: AppMetadata[]) => { +export const useMutationParsers = (engine: Engine, apps: ApplicationDto[]) => { const { data, isLoading, error } = useQueryArray({ query: () => engine.parserConfigService.getParserConfigsForApps(apps), deps: [engine, apps], diff --git a/libs/engine/src/app/services/application/application.entity.ts b/libs/engine/src/app/services/application/application.entity.ts index c72762c0..49173010 100644 --- a/libs/engine/src/app/services/application/application.entity.ts +++ b/libs/engine/src/app/services/application/application.entity.ts @@ -5,6 +5,7 @@ import { Entity } from '../base/decorators/entity' import { DocumentId } from '../document/document.entity' import { ParserConfigId } from '../parser-config/parser-config.entity' import { Target } from '../target/target.entity' +import { ApplicationDto } from './dtos/application.dto' export type AppId = EntityId export type AppInstanceId = string @@ -38,13 +39,24 @@ export class AppMetadata extends Base { } = { documents: false, } + + toDto(): ApplicationDto { + return { + ...super.toDto(), + metadata: this.metadata, + targets: this.targets, + parsers: this.parsers, + controller: this.controller, + permissions: this.permissions, + } + } } export type AppInstanceSettings = { isEnabled: boolean } -export type AppWithSettings = AppMetadata & { +export type AppWithSettings = ApplicationDto & { settings: AppInstanceSettings } diff --git a/libs/engine/src/app/services/application/application.service.ts b/libs/engine/src/app/services/application/application.service.ts index 22c2f1e1..156b97a6 100644 --- a/libs/engine/src/app/services/application/application.service.ts +++ b/libs/engine/src/app/services/application/application.service.ts @@ -6,31 +6,44 @@ import { AppInstanceId, AppInstanceSettings, AppInstanceWithSettings, - AppMetadata, AppWithSettings, } from './application.entity' import { ApplicationRepository } from './application.repository' +import { ApplicationDto } from './dtos/application.dto' +import { MutationDto } from '../mutation/dtos/mutation.dto' +import { ApplicationCreateDto } from './dtos/application-create.dto' +import { Transaction } from '../unit-of-work/transaction' export class ApplicationService { constructor(private applicationRepository: ApplicationRepository) {} - public getApplications(): Promise { + public async getApplications(): Promise { // ToDo: out of gas - return this.applicationRepository.getItems() + const apps = await this.applicationRepository.getItems() + return apps.map((app) => app.toDto()) } - public getApplication(appId: AppId): Promise { - return this.applicationRepository.getItem(appId) + public async getApplication(appId: AppId): Promise { + const app = await this.applicationRepository.getItem(appId) + return app?.toDto() ?? null } - public async getAppsFromMutation(mutation: Mutation): Promise { + public async getAppsFromMutation(mutation: MutationDto): Promise { return Promise.all( mutation.apps.map((appInstance) => this._getAppInstanceWithSettings(mutation.id, appInstance)) ).then((apps) => apps.filter((app) => app !== null) as AppInstanceWithSettings[]) } - public filterSuitableApps(appsToCheck: AppMetadata[], context: IContextNode): AppMetadata[] { - const suitableApps: AppMetadata[] = [] + async createApplication(dto: ApplicationCreateDto, tx?: Transaction): Promise { + const app = await this.applicationRepository.constructItem(dto) + return this.applicationRepository.createItem(app, tx) + } + + public filterSuitableApps( + appsToCheck: ApplicationDto[], + context: IContextNode + ): ApplicationDto[] { + const suitableApps: ApplicationDto[] = [] for (const app of appsToCheck) { const suitableTargets = app.targets.filter((target) => diff --git a/libs/engine/src/app/services/application/dtos/application-create.dto.ts b/libs/engine/src/app/services/application/dtos/application-create.dto.ts new file mode 100644 index 00000000..2e0d05ce --- /dev/null +++ b/libs/engine/src/app/services/application/dtos/application-create.dto.ts @@ -0,0 +1,14 @@ +import { EntityMetadata } from '../../../common/entity-metadata' +import { BaseCreateDto } from '../../base/base-create.dto' +import { ParserConfigId } from '../../parser-config/parser-config.entity' +import { AnyParserValue, AppId, AppMetadataTarget } from '../application.entity' + +export type ApplicationCreateDto = BaseCreateDto & { + metadata: EntityMetadata + targets: AppMetadataTarget[] + parsers: typeof AnyParserValue | ParserConfigId[] | null + controller: string | null + permissions: { + documents: boolean + } +} diff --git a/libs/engine/src/app/services/application/dtos/application.dto.ts b/libs/engine/src/app/services/application/dtos/application.dto.ts new file mode 100644 index 00000000..8400d242 --- /dev/null +++ b/libs/engine/src/app/services/application/dtos/application.dto.ts @@ -0,0 +1,14 @@ +import { EntityMetadata } from '../../../common/entity-metadata' +import { BaseDto } from '../../base/base.dto' +import { ParserConfigId } from '../../parser-config/parser-config.entity' +import { AnyParserValue, AppId, AppMetadataTarget } from '../application.entity' + +export type ApplicationDto = BaseDto & { + metadata: EntityMetadata + targets: AppMetadataTarget[] + parsers: typeof AnyParserValue | ParserConfigId[] | null + controller: string | null + permissions: { + documents: boolean + } +} diff --git a/libs/engine/src/app/services/base/base-create.dto.ts b/libs/engine/src/app/services/base/base-create.dto.ts index f2d1a66f..c4ec00c0 100644 --- a/libs/engine/src/app/services/base/base-create.dto.ts +++ b/libs/engine/src/app/services/base/base-create.dto.ts @@ -1,8 +1 @@ -export type BaseCreateDto = - | { - authorId: string - localId: string - } - | { - id: string - } +export type BaseCreateDto = {} diff --git a/libs/engine/src/app/services/base/base.entity.ts b/libs/engine/src/app/services/base/base.entity.ts index d677aae8..1e96a532 100644 --- a/libs/engine/src/app/services/base/base.entity.ts +++ b/libs/engine/src/app/services/base/base.entity.ts @@ -1,3 +1,4 @@ +import { BaseDto } from './base.dto' import { getEntity } from './decorators/entity' const KeyDelimiter = '/' @@ -29,7 +30,10 @@ export class Base { return getEntity(this.constructor).name } - static create(this: new () => T, data: Partial): T { + static create( + this: new () => T, + data: Partial & (Pick | Pick) + ): T { const instance = new this() Object.assign(instance, data) return instance @@ -40,4 +44,14 @@ export class Base { Object.assign(copyInstance, this, data) return copyInstance } + + toDto(): BaseDto { + return { + id: this.id, + localId: this.localId, + authorId: this.authorId, + blockNumber: this.blockNumber, + timestamp: this.timestamp, + } + } } diff --git a/libs/engine/src/app/services/base/base.repository.ts b/libs/engine/src/app/services/base/base.repository.ts index f4091ede..0d25e4d8 100644 --- a/libs/engine/src/app/services/base/base.repository.ts +++ b/libs/engine/src/app/services/base/base.repository.ts @@ -5,6 +5,7 @@ import { getEntity } from './decorators/entity' import { ColumnType, getColumn } from './decorators/column' import { mergeDeep } from '../../common/merge-deep' import { Transaction } from '../unit-of-work/transaction' +import { EntityMetadata } from '../../common/entity-metadata' // ToDo: parametrize? const ProjectIdKey = 'dapplets.near' @@ -187,6 +188,28 @@ export class BaseRepository { await this._commitOrQueue(nullData, tx) } + async constructItem( + item: Omit & { metadata: EntityMetadata } + ): Promise { + if (!item?.metadata?.name) { + throw new Error('Metadata name is required') + } + + const localId = BaseRepository._normalizeNameToLocalId(item.metadata.name) + + // ToDo: have to make signer public for it + const authorId = await this.socialDb.signer.getAccountId() + + if (!authorId) { + throw new Error('User is not logged in') + } + + // @ts-ignore + const entity: T = this.EntityType.create({ ...item, localId, authorId }) + + return entity + } + private async _commitOrQueue(dataToSave: Value, tx?: Transaction) { if (tx) { tx.queue(dataToSave) @@ -315,4 +338,9 @@ export class BaseRepository { private static _clearObjectFromMeta(obj: Value): Value { return this._replaceEmptyKeyWithValue(this._removeBlockKeys(obj)) } + + private static _normalizeNameToLocalId(name: string): string { + // allow only alphanumeric + return name.replace(/[^a-zA-Z0-9]/g, '') + } } diff --git a/libs/engine/src/app/services/document/document.entity.ts b/libs/engine/src/app/services/document/document.entity.ts index 7bc274e6..78c5fe09 100644 --- a/libs/engine/src/app/services/document/document.entity.ts +++ b/libs/engine/src/app/services/document/document.entity.ts @@ -3,6 +3,7 @@ import { AppId } from '../application/application.entity' import { Entity } from '../base/decorators/entity' import { Base, EntityId } from '../base/base.entity' import { Column, ColumnType } from '../base/decorators/column' +import { DocumentDto } from './dtos/document.dto' export type DocumentId = EntityId @@ -15,4 +16,12 @@ export class Document extends Base { @Column({ name: 'open_with', type: ColumnType.Set }) openWith: AppId[] = [] + + toDto(): DocumentDto { + return { + ...super.toDto(), + metadata: this.metadata, + openWith: this.openWith, + } + } } diff --git a/libs/engine/src/app/services/document/document.service.ts b/libs/engine/src/app/services/document/document.service.ts index de0368b9..b97ee61c 100644 --- a/libs/engine/src/app/services/document/document.service.ts +++ b/libs/engine/src/app/services/document/document.service.ts @@ -9,6 +9,7 @@ import { Document, DocumentId } from './document.entity' import { DocumentRepository } from './document.repository' import { UnitOfWorkService } from '../unit-of-work/unit-of-work.service' import { DocumentDto } from './dtos/document.dto' +import { DocumentCreateDto } from './dtos/document-create.dto' export class DocumentSerivce { constructor( @@ -20,24 +21,28 @@ export class DocumentSerivce { async getDocument(globalDocumentId: DocumentId): Promise { const document = await this.documentRepository.getItem(globalDocumentId) - return document ? this._toDto(document) : null + return document?.toDto() ?? null } - async getDocumentsByAppId(globalAppId: AppId): Promise { + async getDocumentsByAppId(globalAppId: AppId): Promise { return this.documentRepository.getItemsByIndex({ openWith: [globalAppId] }) } - async createDocument(document: Document, tx?: Transaction): Promise { - return this.documentRepository.createItem(document, tx) + async createDocument(dto: DocumentCreateDto, tx?: Transaction): Promise { + const document = await this.documentRepository.constructItem(dto) + await this.documentRepository.createItem(document, tx) + return document.toDto() } async createDocumentWithData( mutationId: MutationId, appId: AppId, - document: Document, + dto: DocumentCreateDto, ctx: TransferableContext, dataByAccount: LinkedDataByAccountDto ) { + const document = await this.documentRepository.constructItem(dto) + if (await this.documentRepository.getItem(document.id)) { throw new Error('Document with that ID already exists') } @@ -69,16 +74,4 @@ export class DocumentSerivce { return { mutation } } - - private _toDto(document: Document): DocumentDto { - return { - id: document.id, - localId: document.localId, - authorId: document.authorId, - blockNumber: document.blockNumber, - timestamp: document.timestamp, - metadata: document.metadata, - openWith: document.openWith, - } - } } diff --git a/libs/engine/src/app/services/document/dtos/document-create.dto.ts b/libs/engine/src/app/services/document/dtos/document-create.dto.ts new file mode 100644 index 00000000..049a356f --- /dev/null +++ b/libs/engine/src/app/services/document/dtos/document-create.dto.ts @@ -0,0 +1,8 @@ +import { BaseCreateDto } from '../../base/base-create.dto' +import { AppId } from '../../application/application.entity' +import { DocumentMetadata } from '../document.entity' + +export type DocumentCreateDto = BaseCreateDto & { + metadata: DocumentMetadata + openWith: AppId[] +} diff --git a/libs/engine/src/app/services/mutation/dtos/mutation-create.dto.ts b/libs/engine/src/app/services/mutation/dtos/mutation-create.dto.ts new file mode 100644 index 00000000..db60a83a --- /dev/null +++ b/libs/engine/src/app/services/mutation/dtos/mutation-create.dto.ts @@ -0,0 +1,10 @@ +import { EntityMetadata } from '../../../common/entity-metadata' +import { BaseCreateDto } from '../../base/base-create.dto' +import { Target } from '../../target/target.entity' +import { AppInMutation, Mutation, MutationId } from '../mutation.entity' + +export type MutationCreateDto = BaseCreateDto & { + metadata: EntityMetadata + apps: AppInMutation[] + targets: Target[] +} diff --git a/libs/engine/src/app/services/mutation/dtos/mutation.dto.ts b/libs/engine/src/app/services/mutation/dtos/mutation.dto.ts new file mode 100644 index 00000000..9b10118c --- /dev/null +++ b/libs/engine/src/app/services/mutation/dtos/mutation.dto.ts @@ -0,0 +1,23 @@ +import { BaseDto } from '../../base/base.dto' +import { AppInMutation, Mutation, MutationId } from '../mutation.entity' +import { EntityMetadata } from '../../../common/entity-metadata' +import { Target } from '../../target/target.entity' + +export type MutationDto = BaseDto & { + metadata: EntityMetadata + apps: AppInMutation[] + targets: Target[] +} + +export const toMutationDto = (mutation: Mutation): MutationDto => { + return { + id: mutation.id, + localId: mutation.localId, + authorId: mutation.authorId, + blockNumber: mutation.blockNumber, + timestamp: mutation.timestamp, + metadata: mutation.metadata, + apps: mutation.apps, + targets: mutation.targets, + } +} diff --git a/libs/engine/src/app/services/mutation/mutation.entity.ts b/libs/engine/src/app/services/mutation/mutation.entity.ts index 034e53e5..6e57720a 100644 --- a/libs/engine/src/app/services/mutation/mutation.entity.ts +++ b/libs/engine/src/app/services/mutation/mutation.entity.ts @@ -5,6 +5,7 @@ import { EntityMetadata } from '../../common/entity-metadata' import { Base } from '../base/base.entity' import { Column, ColumnType } from '../base/decorators/column' import { Entity } from '../base/decorators/entity' +import { MutationDto } from './dtos/mutation.dto' export type MutationId = string @@ -23,9 +24,18 @@ export class Mutation extends Base { @Column({ type: ColumnType.Json }) targets: Target[] = [] + + toDto(): MutationDto { + return { + ...super.toDto(), + metadata: this.metadata, + apps: this.apps, + targets: this.targets, + } + } } -export type MutationWithSettings = Mutation & { +export type MutationWithSettings = MutationDto & { settings: { lastUsage: string | null } diff --git a/libs/engine/src/app/services/mutation/mutation.service.ts b/libs/engine/src/app/services/mutation/mutation.service.ts index f8b0b73f..246ec053 100644 --- a/libs/engine/src/app/services/mutation/mutation.service.ts +++ b/libs/engine/src/app/services/mutation/mutation.service.ts @@ -10,6 +10,9 @@ import { PullRequestPayload } from '../notification/types/pull-request' import { generateGuid } from '../../common/generate-guid' import { EntityId } from '../base/base.entity' import { NotificationDto } from '../notification/dtos/notification.dto' +import { MutationDto } from './dtos/mutation.dto' +import { MutationCreateDto } from './dtos/mutation-create.dto' +import { NotificationCreateDto } from '../notification/dtos/notification-create.dto' export type SaveMutationOptions = { applyChangesToOrigin?: boolean @@ -24,16 +27,18 @@ export class MutationService { private nearConfig: { defaultMutationId: string } ) {} - async getMutation(mutationId: string): Promise { + async getMutation(mutationId: string): Promise { const mutation = await this.mutationRepository.getItem(mutationId) - return mutation + return mutation?.toDto() ?? null } - async getMutationsForContext(context: IContextNode): Promise { + async getMutationsForContext(context: IContextNode): Promise { const mutations = await this.mutationRepository.getItems() - return mutations.filter((mutation) => - mutation.targets.some((target) => TargetService.isTargetMet(target, context)) - ) + return mutations + .filter((mutation) => + mutation.targets.some((target) => TargetService.isTargetMet(target, context)) + ) + .map((mutation) => mutation.toDto()) } async getMutationsWithSettings(context: IContextNode): Promise { @@ -80,7 +85,7 @@ export class MutationService { } async createMutation( - mutation: Mutation, + dto: MutationCreateDto, options: SaveMutationOptions = { applyChangesToOrigin: false, askOriginToApplyChanges: false, @@ -88,6 +93,8 @@ export class MutationService { ): Promise { const { applyChangesToOrigin, askOriginToApplyChanges } = options + const mutation = await this.mutationRepository.constructItem(dto) + // ToDo: move to provider? if (await this.mutationRepository.getItem(mutation.id)) { throw new Error('Mutation with that ID already exists') @@ -101,11 +108,11 @@ export class MutationService { ]) ) - return this.populateMutationWithSettings(mutation) + return this.populateMutationWithSettings(mutation.toDto()) } async editMutation( - mutation: Mutation, + dto: MutationDto, options: SaveMutationOptions = { applyChangesToOrigin: false, askOriginToApplyChanges: false, @@ -114,6 +121,8 @@ export class MutationService { ): Promise { const { applyChangesToOrigin, askOriginToApplyChanges } = options + const mutation = Mutation.create(dto) + // ToDo: move to provider? if (!(await this.mutationRepository.getItem(mutation.id))) { throw new Error('Mutation with that ID does not exist') @@ -133,7 +142,7 @@ export class MutationService { await this.unitOfWorkService.runInTransaction(performTx) } - return this.populateMutationWithSettings(mutation) + return this.populateMutationWithSettings(mutation.toDto()) } async acceptPullRequest(notificationId: EntityId): Promise { @@ -184,14 +193,14 @@ export class MutationService { return currentDate } - public async populateMutationWithSettings(mutation: Mutation): Promise { + public async populateMutationWithSettings(mutation: MutationDto): Promise { const lastUsage = await this.mutationRepository.getMutationLastUsage( mutation.id, window.location.hostname ) // ToDo: do not mix MutationWithSettings and Mutation - return (Mutation.create(mutation) as MutationWithSettings).copy({ settings: { lastUsage } }) + return { ...mutation, settings: { lastUsage } } } private async _applyChangesToOrigin(forkedMutation: Mutation, tx?: Transaction) { @@ -238,9 +247,7 @@ export class MutationService { throw new Error('You cannot ask yourself to apply changes') } - const notification = { - authorId: forkAuthorId, - localId: generateGuid(), + const notification: NotificationCreateDto = { type: NotificationType.PullRequest, recipients: [originAuthorId], payload: { diff --git a/libs/engine/src/app/services/notification/notification.entity.ts b/libs/engine/src/app/services/notification/notification.entity.ts index 218e2fde..5ca6059a 100644 --- a/libs/engine/src/app/services/notification/notification.entity.ts +++ b/libs/engine/src/app/services/notification/notification.entity.ts @@ -3,6 +3,8 @@ import { Entity } from '../base/decorators/entity' import { Column, ColumnType } from '../base/decorators/column' import { RegularPayload } from './types/regular' import { PullRequestPayload } from './types/pull-request' +import { Resolution } from './resolution.entity' +import { NotificationDto } from './dtos/notification.dto' export enum NotificationType { Regular = 'regular', @@ -20,6 +22,4 @@ export class Notification extends Base { @Column({ type: ColumnType.Set }) recipients: string[] = [] - - // createdAt: string // 2024-01-01T00:00:00.000Z } diff --git a/libs/engine/src/app/services/notification/notification.service.ts b/libs/engine/src/app/services/notification/notification.service.ts index 604de4fa..6d36dad5 100644 --- a/libs/engine/src/app/services/notification/notification.service.ts +++ b/libs/engine/src/app/services/notification/notification.service.ts @@ -10,6 +10,7 @@ import { NotificationDto } from './dtos/notification.dto' import { NotificationCreateDto } from './dtos/notification-create.dto' import { PullRequestStatus } from './types/pull-request' import { UnitOfWorkService } from '../unit-of-work/unit-of-work.service' +import { generateGuid } from '../../common/generate-guid' export class NotificationService { constructor( @@ -25,7 +26,14 @@ export class NotificationService { } async createNotification(dto: NotificationCreateDto, tx?: Transaction): Promise { - const notification = Notification.create(dto) + const authorId = await this.nearSigner.getAccountId() + if (!authorId) { + throw new Error('Near account is not signed in') + } + + const localId = generateGuid() + + const notification = Notification.create({ ...dto, authorId, localId }) await this.notificationRepository.createItem(notification, tx) } diff --git a/libs/engine/src/app/services/parser-config/parser-config.service.ts b/libs/engine/src/app/services/parser-config/parser-config.service.ts index a748e2ef..734ef5c6 100644 --- a/libs/engine/src/app/services/parser-config/parser-config.service.ts +++ b/libs/engine/src/app/services/parser-config/parser-config.service.ts @@ -1,4 +1,5 @@ -import { AnyParserValue, AppMetadata } from '../application/application.entity' +import { AnyParserValue } from '../application/application.entity' +import { ApplicationDto } from '../application/dtos/application.dto' import { ParserConfig, ParserConfigId } from './parser-config.entity' import { ParserConfigRepository } from './parser-config.repository' @@ -14,7 +15,7 @@ export class ParserConfigService { return this.parserConfigRepository.getItems() } - public async getParserConfigsForApps(apps: AppMetadata[]): Promise { + public async getParserConfigsForApps(apps: ApplicationDto[]): Promise { const namespaces = new Set() for (const app of apps) { diff --git a/libs/engine/src/app/services/social-db/social-db.service.ts b/libs/engine/src/app/services/social-db/social-db.service.ts index f4646301..4729f933 100644 --- a/libs/engine/src/app/services/social-db/social-db.service.ts +++ b/libs/engine/src/app/services/social-db/social-db.service.ts @@ -106,12 +106,12 @@ const removeDuplicates = (data: any, prevData: any) => { */ export class SocialDbService { constructor( - private _signer: NearSigner, + public signer: NearSigner, private _contractName: string ) {} async get(keys: string[], options: { withBlockHeight?: boolean } = {}): Promise { - return await this._signer.view(this._contractName, 'get', { + return await this.signer.view(this._contractName, 'get', { keys, options: { with_block_height: options.withBlockHeight, @@ -120,7 +120,7 @@ export class SocialDbService { } async keys(keys: string[]): Promise { - const response = await this._signer.view(this._contractName, 'keys', { + const response = await this.signer.view(this._contractName, 'keys', { keys, }) @@ -139,7 +139,7 @@ export class SocialDbService { } const [accountId] = accountIds - const signedAccountId = await this._signer.getAccountId() + const signedAccountId = await this.signer.getAccountId() if (!signedAccountId) { throw new Error('User is not logged in') @@ -175,7 +175,7 @@ export class SocialDbService { deposit = deposit.add(ExtraStorageForSession) } - await this._signer.call( + await this.signer.call( this._contractName, 'set', { data }, @@ -194,12 +194,12 @@ export class SocialDbService { // ToDo: approximate timestamp getTimestampByBlockHeight(blockHeight: number): number { // ToDo: time reference was private, fix it - const { avgBlockTime, height, timestamp } = this._signer.nearConfig.timeReference + const { avgBlockTime, height, timestamp } = this.signer.nearConfig.timeReference return (blockHeight - height) * avgBlockTime + timestamp } private async _getAccountStorage(accountId: string): Promise { - const resp = await this._signer.view(this._contractName, 'get_account_storage', { + const resp = await this.signer.view(this._contractName, 'get_account_storage', { account_id: accountId, }) @@ -211,7 +211,7 @@ export class SocialDbService { private async _fetchCurrentData(data: any) { const keys = extractKeys(data) - return await this._signer.view(this._contractName, 'get', { keys }) + return await this.signer.view(this._contractName, 'get', { keys }) } // Utils diff --git a/libs/engine/src/index.ts b/libs/engine/src/index.ts index 05cfe107..29d9fd4c 100644 --- a/libs/engine/src/index.ts +++ b/libs/engine/src/index.ts @@ -1,10 +1,13 @@ export * as customElements from './custom-elements' -export { Mutation, MutationWithSettings } from './app/services/mutation/mutation.entity' +export { MutationWithSettings } from './app/services/mutation/mutation.entity' +export { MutationDto } from './app/services/mutation/dtos/mutation.dto' +export { MutationCreateDto } from './app/services/mutation/dtos/mutation-create.dto' export { - AppMetadata, AppWithSettings, AppInstanceWithSettings, } from './app/services/application/application.entity' +export { ApplicationDto } from './app/services/application/dtos/application.dto' +export { ApplicationCreateDto } from './app/services/application/dtos/application-create.dto' export { LocalStorage } from './app/services/local-db/local-storage' export { IStorage } from './app/services/local-db/local-storage' export { App } from './app/app' @@ -19,7 +22,8 @@ export { ShadowDomWrapper } from './app/components/shadow-dom-wrapper' export { EngineConfig } from './engine' export { App as MutableWebProvider } from './app/app' export { useAppDocuments } from './app/contexts/mutable-web-context/use-app-documents' -export { Document } from './app/services/document/document.entity' +export { DocumentDto } from './app/services/document/dtos/document.dto' +export { DocumentCreateDto } from './app/services/document/dtos/document-create.dto' export { NotificationProvider, useNotifications, diff --git a/libs/shared-components/src/mini-overlay/index.tsx b/libs/shared-components/src/mini-overlay/index.tsx index 41a4f7b0..765546be 100644 --- a/libs/shared-components/src/mini-overlay/index.tsx +++ b/libs/shared-components/src/mini-overlay/index.tsx @@ -1,4 +1,4 @@ -import { AppWithSettings, Mutation } from '@mweb/engine' +import { AppWithSettings, MutationDto } from '@mweb/engine' import { useAccountId } from 'near-social-vm' import React, { FC, ReactElement, useState, useRef } from 'react' import Spinner from 'react-bootstrap/Spinner' @@ -339,7 +339,7 @@ interface IAppSwitcherProps extends IMutationAppsControl { } interface IMiniOverlayProps extends Partial { - baseMutation: Mutation | null + baseMutation: MutationDto | null mutationApps: AppWithSettings[] children: ReactElement trackingRefs?: Set>