diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 742193121..395c7116f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -17,6 +17,8 @@ jobs: - name: Install nodejs dependencies run: | npm run web-install + npm run zod-models-install + npm run zod-models-build shell: bash - name: run unit tests run: | diff --git a/.gitignore b/.gitignore index 72ce68edd..c6a033976 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ user2-data user-data electron/web +zod-models/dist + web/dist/fonts web/dist/images web/dist/*.js diff --git a/electron/src/index.ts b/electron/src/index.ts index b854bd41f..44e5cdefe 100644 --- a/electron/src/index.ts +++ b/electron/src/index.ts @@ -218,6 +218,7 @@ ipcMain.handle('getVersion', () => { version: `v${app.getVersion()}`, platform: process.platform, arch: process.arch, + integrityVersion: INTEGRITY_VERSION_NUMBER, } }) diff --git a/electron/src/paths.ts b/electron/src/paths.ts index deca22d91..bf15d87c3 100644 --- a/electron/src/paths.ts +++ b/electron/src/paths.ts @@ -31,6 +31,7 @@ const PREV_VER_USER_DATA_MIGRATION_FILE_PATHS = [ // Acorn 6 path.join(USER_DATA_PATH, `${MIGRATION_FILE_NAME_PREFIX}9`), // uncomment the below line for development testing + // of migration-feature // path.join(USER_DATA_PATH, `${MIGRATION_FILE_NAME_PREFIX}${INTEGRITY_VERSION_NUMBER}`), ] diff --git a/package.json b/package.json index cbc4516b2..043048607 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "url": "git+https://github.com/h-be/acorn.git" }, "scripts": { - "install-deps": "npm install && npm run web-install && npm run electron-install && npm run electron-tsc", + "install-deps": "npm install && npm run web-install && npm run zod-models-install && npm run electron-install && npm run electron-tsc", "dev": "pm2-dev pm2.config.js", "user-data-reset": "rm -rf user-data && mkdir user-data", "web-install": "cd web && npm install", + "zod-models-install": "cd zod-models && npm install", + "zod-models-build": "cd zod-models && npm run build", "web": "cd web && WEB_PORT=8081 ADMIN_WS_PORT=1101 APP_WS_PORT=8101 npx webpack-dev-server --config webpack.dev.js", "web2": "cd web && WEB_PORT=8082 ADMIN_WS_PORT=1102 APP_WS_PORT=8102 npx webpack-dev-server --config webpack.dev.js", "web3": "cd web && WEB_PORT=8083 ADMIN_WS_PORT=1103 APP_WS_PORT=8103 npx webpack-dev-server --config webpack.dev.js", diff --git a/web/package-lock.json b/web/package-lock.json index e4ded007e..43418fa29 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -40,7 +40,9 @@ "reselect": "^4.1.5", "resolve-url-loader": "^5.0.0", "type-fest": "^0.17.0", - "use-onclickoutside": "^0.3.1" + "use-onclickoutside": "^0.3.1", + "zod": "^3.21.4", + "zod-models": "file:../zod-models" }, "devDependencies": { "@babel/core": "^7.14.0", @@ -85,6 +87,16 @@ "webpack-merge": "^5.8.0" } }, + "../zod-models": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "zod": "^3.21.4" + }, + "devDependencies": { + "typescript": "^5.1.6" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -21557,6 +21569,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-models": { + "resolved": "../zod-models", + "link": true + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -37338,6 +37362,18 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" + }, + "zod-models": { + "version": "file:../zod-models", + "requires": { + "typescript": "^5.1.6", + "zod": "^3.21.4" + } + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/web/package.json b/web/package.json index 303a88ee0..853cd80bc 100644 --- a/web/package.json +++ b/web/package.json @@ -79,7 +79,9 @@ "reselect": "^4.1.5", "resolve-url-loader": "^5.0.0", "type-fest": "^0.17.0", - "use-onclickoutside": "^0.3.1" + "use-onclickoutside": "^0.3.1", + "zod": "^3.21.4", + "zod-models": "file:../zod-models" }, "scripts": { "storybook": "storybook dev -p 6006", diff --git a/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx b/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx index 6df39659c..e0c34b8d0 100644 --- a/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx +++ b/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx @@ -364,6 +364,7 @@ const EvDetails: React.FC = ({ projectId={projectId} onClose={() => setEditAssignees(false)} activeAgentPubKey={activeAgentPubKey} + profilesPresent={presentMembers} people={people} outcomeActionHash={outcomeActionHash} createOutcomeMember={createOutcomeMember} diff --git a/web/src/components/Footer/Footer.scss b/web/src/components/Footer/Footer.scss index 6195f41df..a33689ed0 100644 --- a/web/src/components/Footer/Footer.scss +++ b/web/src/components/Footer/Footer.scss @@ -1,5 +1,6 @@ .footer { max-height: 48px; + z-index: 3; } /* Buttom Left Panel */ diff --git a/web/src/components/MapViewingOptions/MapViewingOptions.tsx b/web/src/components/MapViewingOptions/MapViewingOptions.tsx index 2807f4046..b282c123e 100644 --- a/web/src/components/MapViewingOptions/MapViewingOptions.tsx +++ b/web/src/components/MapViewingOptions/MapViewingOptions.tsx @@ -49,11 +49,11 @@ const MapViewingOptions: React.FC = ({ selectedOptionId={selectedLayeringAlgo} options={[ { - id: LayeringAlgorithm.LongestPath, + id: "LongestPath", text: 'Minimum Height', }, { - id: LayeringAlgorithm.CoffmanGraham, + id: "CoffmanGraham", text: 'Constrained Width', }, ]} diff --git a/web/src/components/MigrationProgress/MigrationProgress.scss b/web/src/components/MigrationProgress/MigrationProgress.scss index 93e5217d2..383799db7 100644 --- a/web/src/components/MigrationProgress/MigrationProgress.scss +++ b/web/src/components/MigrationProgress/MigrationProgress.scss @@ -6,7 +6,7 @@ position: fixed; top: 0; background: var(--bg-color-canvas); - z-index: 10000; + z-index: 2; } .run-update-screen { diff --git a/web/src/components/PeoplePicker/PeoplePicker.tsx b/web/src/components/PeoplePicker/PeoplePicker.tsx index 191086637..96dac9a25 100644 --- a/web/src/components/PeoplePicker/PeoplePicker.tsx +++ b/web/src/components/PeoplePicker/PeoplePicker.tsx @@ -15,6 +15,7 @@ export type PeoplePickerProps = { isOutcomeMember: boolean outcomeMemberActionHash: ActionHashB64 })[] + profilesPresent: AgentPubKeyB64[] outcomeActionHash: ActionHashB64 createOutcomeMember: ( outcomeActionHash: ActionHashB64, @@ -27,6 +28,7 @@ export type PeoplePickerProps = { const PeoplePicker: React.FC = ({ activeAgentPubKey, people, + profilesPresent, outcomeActionHash, createOutcomeMember, deleteOutcomeMember, @@ -97,6 +99,12 @@ const PeoplePicker: React.FC = ({ activeAgentPubKey ) } + // check if the profile is in the + // list of present profiles + // (presence being "has the project open presently") + const isProfilePresent = !!profilesPresent.find( + (presentprofile) => person.agentPubKey === presentprofile + ) return (
  • = ({ avatarUrl={person.avatarUrl} imported={person.isImported} size="medium" - withStatus selfAssignedStatus={person.status} + disconnected={!isProfilePresent} + withStatus={isProfilePresent} />
    diff --git a/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx b/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx index 796af3621..a6244c7d8 100644 --- a/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx +++ b/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx @@ -117,7 +117,7 @@ function EditProjectForm({ export default function ProjectSettingsModal({ showModal, onClose, - project = {} as WithActionHash, + project, updateProjectMeta, openInviteMembersModal, cellIdString, diff --git a/web/src/components/UpdateModal/UpdateModal.tsx b/web/src/components/UpdateModal/UpdateModal.tsx index df1307506..ac699dbc2 100644 --- a/web/src/components/UpdateModal/UpdateModal.tsx +++ b/web/src/components/UpdateModal/UpdateModal.tsx @@ -32,6 +32,7 @@ const UpdateModal: React.FC = ({ }) => { const history = useHistory() const runUpdate = () => { + onClose() history.push('/run-update') } diff --git a/web/src/components/UpdatePromptModal/UpdatePromptModal.js b/web/src/components/UpdatePromptModal/UpdatePromptModal.js deleted file mode 100644 index 32cf2e0bd..000000000 --- a/web/src/components/UpdatePromptModal/UpdatePromptModal.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import './UpdatePromptModal.scss' - -import Modal, { ModalContent } from '../Modal/Modal' -import { useHistory } from 'react-router-dom' - -export default function UpdatePromptModal({ show, onClose }) { - const history = useHistory() - const updateDetails = ( -
    -
    About this update
    -
    -
  • - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. -
  • -
  • - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. -
  • -
  • - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor -
  • -
  • fixed some bugs
  • -
  • RSM refactoring
  • - - - ) - - const runUpdate = () => { - history.push('/run-update') - } - - return ( - <> - - - - - ) -} diff --git a/web/src/components/UpdatePromptModal/UpdatePromptModal.scss b/web/src/components/UpdatePromptModal/UpdatePromptModal.scss deleted file mode 100644 index a72fc3ea7..000000000 --- a/web/src/components/UpdatePromptModal/UpdatePromptModal.scss +++ /dev/null @@ -1,35 +0,0 @@ -.update-prompt-modal.modal-wrapper { - width: 540px; - min-height: 540px; - padding: 70px; -} - -.modal-content { - /* font-family: 'PlusJakartaSans-medium', Helvetica, Sans-Serif; */ - line-height: 1.45; -} - -.update-details-wrapper { - color: #696969; - margin: 30px 0; -} - -.update-details-title { - font-family: 'PlusJakartaSans-bold', Helvetica, Sans-Serif; - margin-bottom: 8px; -} - -.update-details-content { - font-size: 14px; - line-height: 1.45; -} - -.update-details-content li { - list-style-type: '🌰'; - padding-inline-start: 0.5ch; - margin-bottom: 2px; -} - -li::marker { - font-size: 12px; -} \ No newline at end of file diff --git a/web/src/drawing/layoutFormula.ts b/web/src/drawing/layoutFormula.ts index 15fa32d05..4b06dca49 100644 --- a/web/src/drawing/layoutFormula.ts +++ b/web/src/drawing/layoutFormula.ts @@ -97,9 +97,9 @@ function layoutForGraph( // TODO: add back in when we figure out how to not have it crash // case LayeringAlgorithm.Simplex: // return simplex - case LayeringAlgorithm.LongestPath: + case "LongestPath": return longestPath - case LayeringAlgorithm.CoffmanGraham: + case "CoffmanGraham": return coffmanGraham default: return coffmanGraham diff --git a/web/src/hooks/useFinishMigrationChecker.ts b/web/src/hooks/useFinishMigrationChecker.ts index ce8ed096b..e33af4962 100644 --- a/web/src/hooks/useFinishMigrationChecker.ts +++ b/web/src/hooks/useFinishMigrationChecker.ts @@ -26,6 +26,7 @@ export default function useFinishMigrationChecker(): { } setHasChecked(true) } else { + // mock here if we want to test setHasChecked(true) } } diff --git a/web/src/hooks/useVersionChecker.ts b/web/src/hooks/useVersionChecker.ts index b10f42058..9f2d35661 100644 --- a/web/src/hooks/useVersionChecker.ts +++ b/web/src/hooks/useVersionChecker.ts @@ -98,6 +98,7 @@ export default function useVersionChecker( isTest?: boolean ): { currentVersion: string + integrityVersion: number platform: string arch: string newReleaseVersion: string @@ -105,6 +106,7 @@ export default function useVersionChecker( sizeForPlatform: string } { const [currentVersion, setCurrentVersion] = useState('') + const [integrityVersion, setIntegrityVersion] = useState(null) const [platform, setPlatform] = useState('') const [arch, setArch] = useState('') const [newReleaseVersion, setNewReleaseVersion] = useState('') @@ -119,10 +121,12 @@ export default function useVersionChecker( .then( (currentVersionInfo: { version: string + integrityVersion: number platform: string arch: string }) => { setCurrentVersion(currentVersionInfo.version) + setIntegrityVersion(currentVersionInfo.integrityVersion) setPlatform(currentVersionInfo.platform) setArch(currentVersionInfo.arch) } @@ -134,7 +138,7 @@ export default function useVersionChecker( // to see if there is any new update available for the app useEffect(() => { if (isTest) { - setNewReleaseVersion('v1.0.0') + setNewReleaseVersion('v12.0.0') setReleaseNotes('release notes') setSizeForPlatform('1mb') return @@ -160,6 +164,7 @@ export default function useVersionChecker( return newReleaseVersion ? { currentVersion, + integrityVersion, platform, arch, newReleaseVersion, diff --git a/web/src/migrating/export.ts b/web/src/migrating/export.ts index a886c0c85..b8e56abaf 100644 --- a/web/src/migrating/export.ts +++ b/web/src/migrating/export.ts @@ -1,36 +1,14 @@ +import { AllProjectsDataExport, ProjectExportDataV1 } from 'zod-models' import constructProjectDataFetchers from '../api/projectDataFetchers' import ProjectsZomeApi from '../api/projectsApi' import { getAppWs } from '../hcWebsockets' -import { ProjectConnectionsState } from '../redux/persistent/projects/connections/reducer' -import { ProjectEntryPointsState } from '../redux/persistent/projects/entry-points/reducer' -import { ProjectOutcomeCommentsState } from '../redux/persistent/projects/outcome-comments/reducer' -import { ProjectOutcomeMembersState } from '../redux/persistent/projects/outcome-members/reducer' -import { ProjectOutcomesState } from '../redux/persistent/projects/outcomes/reducer' import { RootState } from '../redux/reducer' -import { Profile, ProjectMeta, Tag } from '../types' -import { ActionHashB64, CellIdString, WithActionHash } from '../types/shared' +import { ProjectMeta } from '../types' +import { ActionHashB64, CellIdString } from '../types/shared' import { cellIdFromString } from '../utils' export type ExportType = 'csv' | 'json' -export type ProjectExportDataV1 = { - projectMeta: WithActionHash - outcomes: ProjectOutcomesState - connections: ProjectConnectionsState - outcomeMembers: ProjectOutcomeMembersState - outcomeComments: ProjectOutcomeCommentsState - entryPoints: ProjectEntryPointsState - tags: { - [actionHash: string]: WithActionHash - } -} - -export type AllProjectsDataExport = { - myProfile: Profile - projects: ProjectExportDataV1[] - integrityVersion: string -} - export async function updateProjectMeta( projectMeta: ProjectMeta, actionHash: ActionHashB64, @@ -52,7 +30,7 @@ export async function internalExportProjectsData( store: any, toVersion: string, onStep: (completed: number, toComplete: number) => void, - integrityVersion: string + integrityVersion: number ): Promise { const initialState: RootState = store.getState() @@ -79,15 +57,30 @@ export async function internalExportProjectsData( store.dispatch, projectCellId ) - await Promise.all([ - projectDataFetchers.fetchProjectMeta(), - projectDataFetchers.fetchEntryPoints(), - projectDataFetchers.fetchOutcomeComments(), - projectDataFetchers.fetchOutcomeMembers(), - projectDataFetchers.fetchTags(), - projectDataFetchers.fetchOutcomes(), - projectDataFetchers.fetchConnections(), - ]) + try { + + await Promise.all([ + projectDataFetchers.fetchProjectMeta(), + projectDataFetchers.fetchEntryPoints(), + projectDataFetchers.fetchOutcomeComments(), + projectDataFetchers.fetchOutcomeMembers(), + projectDataFetchers.fetchTags(), + projectDataFetchers.fetchOutcomes(), + projectDataFetchers.fetchConnections(), + ]) + } catch (e) { + // fetch project meta will fail if the there IS no project meta + // and in that case we can just skip this project + // this solves for unsynced and uncanceled projects + if (e?.data?.data?.includes('no project meta exists')) { + // throw e + completedTracker++ + onStep(completedTracker, projectCellIds.length) + continue + } else { + throw e + } + } // step 2: collect the data to be exported for each project const allDataFetchedState: RootState = store.getState() const exportProjectData = collectExportProjectDataFunction( @@ -111,6 +104,7 @@ export async function internalExportProjectsData( export default async function exportProjectsData( store: any, toVersion: string, + fromIntegrityVersion: number, onStep: (completed: number, toComplete: number) => void ) { return internalExportProjectsData( @@ -120,8 +114,7 @@ export default async function exportProjectsData( store, toVersion, onStep, - '8' // TODO: replace with INTEGRITY_VERSION_NUMBER - // INTEGRITY_VERSION_NUMBER + fromIntegrityVersion ) } @@ -189,10 +182,7 @@ export function projectDataToCsv(data: ProjectExportDataV1): string { return csvRows.join('\n') } -export function exportDataHref( - type: ExportType, - data: string -): string { +export function exportDataHref(type: ExportType, data: string): string { let blob: Blob if (type === 'csv') { blob = new Blob([data], { diff --git a/web/src/migrating/import/cloneFunctions.ts b/web/src/migrating/import/cloneFunctions.ts index 9c5afb12a..9424d5965 100644 --- a/web/src/migrating/import/cloneFunctions.ts +++ b/web/src/migrating/import/cloneFunctions.ts @@ -11,6 +11,7 @@ import { Tag } from '../../types/tag' import { Outcome } from '../../types/outcome' import { Connection } from '../../types/connection' import { LayeringAlgorithm, ProjectMeta } from '../../types' +import { ProjectMetaWithActionHash } from 'zod-models' export type ActionHashMap = { [oldActionHash: ActionHashB64]: ActionHashB64 } @@ -78,11 +79,10 @@ export const cloneConnection = (outcomeActionHashMap: ActionHashMap) => ( old: WithActionHash ): WithActionHash => { const newParentOutcomeActionHash = outcomeActionHashMap[old.parentActionHash] - // v1.0.4-alpha: childActionHash const newChildOutcomeActionHash = outcomeActionHashMap[old.childActionHash] return { - ...old, + ...old, // technically not needed, but left in case more properties are added in future parentActionHash: newParentOutcomeActionHash, childActionHash: newChildOutcomeActionHash, // randomizer used to be a float, but is now an int @@ -95,8 +95,8 @@ export const cloneProjectMeta = ( outcomeActionHashMap: ActionHashMap, agentAddress: AgentPubKeyB64, passphrase: string -) => (old: WithActionHash): WithActionHash => { - const originalTopPriorityOutcomes = old.topPriorityOutcomes +) => (old: ProjectMetaWithActionHash): WithActionHash => { + const originalTopPriorityOutcomes = old['topPriorityOutcomes'] return { ...old, // the question mark operator for backwards compatibility @@ -106,9 +106,9 @@ export const cloneProjectMeta = ( .filter((address) => address) : [], // add a fallback layering algorithm in case the project has none - layeringAlgorithm: old.layeringAlgorithm - ? old.layeringAlgorithm - : LayeringAlgorithm.LongestPath, + layeringAlgorithm: old['layeringAlgorithm'] + ? old['layeringAlgorithm'] + : "LongestPath", createdAt: Date.now(), creatorAgentPubKey: agentAddress, passphrase: passphrase, diff --git a/web/src/migrating/import/import.ts b/web/src/migrating/import/import.ts index d9d28ab8d..c0b609f94 100644 --- a/web/src/migrating/import/import.ts +++ b/web/src/migrating/import/import.ts @@ -1,15 +1,24 @@ +import { z } from 'zod' import { getAppWs } from '../../hcWebsockets' import { cellIdFromString } from '../../utils' import { RootState } from '../../redux/reducer' -import { AllProjectsDataExport } from '../export' import { createWhoami } from '../../redux/persistent/profiles/who-am-i/actions' import { installProject } from '../../projects/installProject' -import { joinProjectCellId } from '../../redux/persistent/cells/actions' import { createProfilesZomeApi } from './zomeApiCreators' import { installProjectAndImport } from './installProjectAndImport' import ProfilesZomeApi from '../../api/profilesApi' +import { AllProjectsDataExport, AllProjectsDataExportSchema } from 'zod-models' import { internalJoinProject } from '../../projects/joinProject' +const stringToJSONSchema = z.string().transform((str, ctx): any => { + try { + return JSON.parse(str) + } catch (e) { + ctx.addIssue({ code: 'custom', message: 'Invalid JSON' }) + return z.NEVER + } +}) + export async function internalImportProjectsData( // dependencies profilesZomeApi: ProfilesZomeApi, @@ -20,7 +29,9 @@ export async function internalImportProjectsData( migrationData: string, onStep: (completed: number, toComplete: number) => void ) { - const migrationDataParsed: AllProjectsDataExport = JSON.parse(migrationData) + const migrationDataParsed: AllProjectsDataExport = AllProjectsDataExportSchema.parse( + stringToJSONSchema.parse(migrationData) + ) const initialState: RootState = store.getState() const myAgentPubKey = initialState.agentAddress diff --git a/web/src/migrating/import/installProjectAndImport.ts b/web/src/migrating/import/installProjectAndImport.ts index 525035d2a..6c767e6c5 100644 --- a/web/src/migrating/import/installProjectAndImport.ts +++ b/web/src/migrating/import/installProjectAndImport.ts @@ -1,9 +1,9 @@ +import { ProjectExportDataV1 } from 'zod-models' import ProjectsZomeApi from '../../api/projectsApi' import { finalizeCreateProject } from '../../projects/createProject' import { getAppWs } from '../../hcWebsockets' import { installProject } from '../../projects/installProject' import { AgentPubKeyB64 } from '../../types/shared' -import { ProjectExportDataV1 } from '../export' import { cloneProjectMeta } from './cloneFunctions' import { createActionHashMapAndImportProjectData } from './createActionHashMapAndImportProjectData' diff --git a/web/src/redux/ephemeral/animations/layout.ts b/web/src/redux/ephemeral/animations/layout.ts index 491074cbc..557abb310 100644 --- a/web/src/redux/ephemeral/animations/layout.ts +++ b/web/src/redux/ephemeral/animations/layout.ts @@ -71,7 +71,7 @@ export default function performLayoutAnimation( const projectMeta = nextState.projects.projectMeta const layeringAlgorithm = - projectMeta[projectId]?.layeringAlgorithm || LayeringAlgorithm.LongestPath + projectMeta[projectId]?.layeringAlgorithm || "LongestPath" // this is our final destination layout // that we'll be animating to const newLayout = layoutFormula( diff --git a/web/src/redux/ephemeral/map-view-settings/reducer.ts b/web/src/redux/ephemeral/map-view-settings/reducer.ts index c75ab0798..7e171b01a 100644 --- a/web/src/redux/ephemeral/map-view-settings/reducer.ts +++ b/web/src/redux/ephemeral/map-view-settings/reducer.ts @@ -16,7 +16,7 @@ export interface CollapsedOutcomesState { const defaultState: CollapsedOutcomesState = { hiddenAchievedOutcomes: [], hiddenSmallOutcomes: [], - selectedLayeringAlgo: LayeringAlgorithm.LongestPath, + selectedLayeringAlgo: "LongestPath", } export default function ( diff --git a/web/src/redux/persistent/projects/connections/reducer.ts b/web/src/redux/persistent/projects/connections/reducer.ts index a65703090..cf3a42aa9 100644 --- a/web/src/redux/persistent/projects/connections/reducer.ts +++ b/web/src/redux/persistent/projects/connections/reducer.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import { z } from 'zod' import { CREATE_CONNECTION, @@ -6,23 +7,45 @@ import { UPDATE_CONNECTION, DELETE_CONNECTION, } from './actions' -import { CREATE_OUTCOME_WITH_CONNECTION, DELETE_OUTCOME_FULLY } from '../outcomes/actions' +import { + CREATE_OUTCOME_WITH_CONNECTION, + DELETE_OUTCOME_FULLY, +} from '../outcomes/actions' import { isCrud, crudReducer } from '../../crudRedux' import { CellIdString, WithActionHash } from '../../../../types/shared' -import { Connection, CreateOutcomeWithConnectionOutput, DeleteOutcomeFullyResponse } from '../../../../types' +import { + Connection, + ConnectionSchema, + CreateOutcomeWithConnectionOutput, + DeleteOutcomeFullyResponse, +} from '../../../../types' +export const ProjectConnectionsStateSchema = z.record( + z.object({ actionHash: z.string() }).merge(ConnectionSchema) +) export type ProjectConnectionsState = { [actionHash: string]: WithActionHash } -export type ConnectionsState = { +export type ConnectionsState = { [cellId: CellIdString]: ProjectConnectionsState } const defaultState: ConnectionsState = {} -export default function (state: ConnectionsState = defaultState, action): ConnectionsState { +export default function ( + state: ConnectionsState = defaultState, + action +): ConnectionsState { // start out by checking whether this a standard CRUD operation - if (isCrud(action, CREATE_CONNECTION, FETCH_CONNECTIONS, UPDATE_CONNECTION, DELETE_CONNECTION)) { + if ( + isCrud( + action, + CREATE_CONNECTION, + FETCH_CONNECTIONS, + UPDATE_CONNECTION, + DELETE_CONNECTION + ) + ) { return crudReducer( state, action, @@ -49,7 +72,8 @@ export default function (state: ConnectionsState = defaultState, action): Connec ...state[cellId], [createOutcomeWithConnection.maybeConnection.actionHash]: { ...createOutcomeWithConnection.maybeConnection.entry, - actionHash: createOutcomeWithConnection.maybeConnection.actionHash, + actionHash: + createOutcomeWithConnection.maybeConnection.actionHash, }, }, } @@ -66,7 +90,8 @@ export default function (state: ConnectionsState = defaultState, action): Connec ...state, [cellId]: _.pickBy( state[cellId], - (_value, key) => deleteOutcomeFullyResponse.deletedConnections.indexOf(key) === -1 + (_value, key) => + deleteOutcomeFullyResponse.deletedConnections.indexOf(key) === -1 ), } default: diff --git a/web/src/redux/persistent/projects/entry-points/reducer.ts b/web/src/redux/persistent/projects/entry-points/reducer.ts index a15828fe1..763aebcb0 100644 --- a/web/src/redux/persistent/projects/entry-points/reducer.ts +++ b/web/src/redux/persistent/projects/entry-points/reducer.ts @@ -1,4 +1,4 @@ - +import { z } from 'zod' import _ from 'lodash' import { @@ -11,10 +11,29 @@ import { } from './actions' import { DELETE_OUTCOME_FULLY } from '../outcomes/actions' import { isCrud, crudReducer } from '../../crudRedux' -import { Action, CellIdString, ActionHashB64, WithActionHash } from '../../../../types/shared' -import { DeleteOutcomeFullyResponse, EntryPoint, EntryPointDetails } from '../../../../types' +import { + Action, + CellIdString, + ActionHashB64, + WithActionHash, +} from '../../../../types/shared' +import { + DeleteOutcomeFullyResponse, + EntryPoint, + EntryPointDetails, + EntryPointSchema, +} from '../../../../types' import { WireRecord } from '../../../../api/hdkCrud' +//ProjectEntryPointsStateSchema +export const ProjectEntryPointsStateSchema = z.record( + z + .object({ + actionHash: z.string(), + }) + .merge(EntryPointSchema) +) + // state is at the highest level an object with cellIds // which are like Projects... EntryPoints exist within Projects // so they are contained per project in the top level state @@ -30,7 +49,10 @@ type EntryPointsState = { } const defaultState: EntryPointsState = {} -export default function (state: EntryPointsState = defaultState, action: EntryPointsAction | Action): EntryPointsState { +export default function ( + state: EntryPointsState = defaultState, + action: EntryPointsAction | Action +): EntryPointsState { if ( isCrud( action, @@ -57,7 +79,7 @@ export default function (state: EntryPointsState = defaultState, action: EntryPo case FETCH_ENTRY_POINT_DETAILS: cellIdString = action.meta.cellIdString const entryPointDetails = payload as EntryPointDetails - const mapped = entryPointDetails.entryPoints.map(r => { + const mapped = entryPointDetails.entryPoints.map((r) => { return { ...r.entry, actionHash: r.actionHash, @@ -83,7 +105,8 @@ export default function (state: EntryPointsState = defaultState, action: EntryPo ...state, [cellIdString]: _.pickBy( state[cellIdString], - (_value, key) => deleteOutcomeResponse.deletedEntryPoints.indexOf(key) === -1 + (_value, key) => + deleteOutcomeResponse.deletedEntryPoints.indexOf(key) === -1 ), } default: diff --git a/web/src/redux/persistent/projects/outcome-comments/reducer.ts b/web/src/redux/persistent/projects/outcome-comments/reducer.ts index 9e6a16a67..2c45bee14 100644 --- a/web/src/redux/persistent/projects/outcome-comments/reducer.ts +++ b/web/src/redux/persistent/projects/outcome-comments/reducer.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import { z } from 'zod' import { CREATE_OUTCOME_COMMENT, @@ -8,10 +9,26 @@ import { } from './actions' import { DELETE_OUTCOME_FULLY } from '../outcomes/actions' import { isCrud, crudReducer } from '../../crudRedux' -import { Action, CellIdString, ActionHashB64, WithActionHash } from '../../../../types/shared' +import { + Action, + CellIdString, + ActionHashB64, + WithActionHash, +} from '../../../../types/shared' import { WireRecord } from '../../../../api/hdkCrud' -import { DeleteOutcomeFullyResponse, OutcomeComment } from '../../../../types' +import { + DeleteOutcomeFullyResponse, + OutcomeComment, + OutcomeCommentSchema, +} from '../../../../types' +export const ProjectOutcomeCommentsStateSchema = z.record( + z + .object({ + actionHash: z.string(), + }) + .merge(OutcomeCommentSchema) +) export type ProjectOutcomeCommentsState = { [actionHash: ActionHashB64]: WithActionHash } @@ -21,7 +38,12 @@ export type OutcomeCommentsState = { } const defaultState: OutcomeCommentsState = {} -export default function (state: OutcomeCommentsState = defaultState, action: Action> | Action): OutcomeCommentsState { +export default function ( + state: OutcomeCommentsState = defaultState, + action: + | Action> + | Action +): OutcomeCommentsState { const { payload, type } = action if ( @@ -30,7 +52,7 @@ export default function (state: OutcomeCommentsState = defaultState, action: Act CREATE_OUTCOME_COMMENT, FETCH_OUTCOME_COMMENTS, UPDATE_OUTCOME_COMMENT, - DELETE_OUTCOME_COMMENT, + DELETE_OUTCOME_COMMENT ) ) { const crudAction = action as Action> @@ -40,7 +62,7 @@ export default function (state: OutcomeCommentsState = defaultState, action: Act CREATE_OUTCOME_COMMENT, FETCH_OUTCOME_COMMENTS, UPDATE_OUTCOME_COMMENT, - DELETE_OUTCOME_COMMENT, + DELETE_OUTCOME_COMMENT ) } @@ -59,7 +81,8 @@ export default function (state: OutcomeCommentsState = defaultState, action: Act ...state, [cellId]: _.pickBy( state[cellId], - (_value, key) => deleteFullyResponse.deletedOutcomeComments.indexOf(key) === -1 + (_value, key) => + deleteFullyResponse.deletedOutcomeComments.indexOf(key) === -1 ), } // DEFAULT diff --git a/web/src/redux/persistent/projects/outcome-members/reducer.ts b/web/src/redux/persistent/projects/outcome-members/reducer.ts index 59ff92fcf..be6a7541f 100644 --- a/web/src/redux/persistent/projects/outcome-members/reducer.ts +++ b/web/src/redux/persistent/projects/outcome-members/reducer.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import { z } from 'zod' import { CREATE_OUTCOME_MEMBER, @@ -15,8 +16,15 @@ import { WithActionHash, } from '../../../../types/shared' import { WireRecord } from '../../../../api/hdkCrud' -import { DeleteOutcomeFullyResponse, OutcomeMember } from '../../../../types' +import { + DeleteOutcomeFullyResponse, + OutcomeMember, + OutcomeMemberSchema, +} from '../../../../types' +export const ProjectOutcomeMembersStateSchema = z.record( + z.object({ actionHash: z.string() }).merge(OutcomeMemberSchema) +) export type ProjectOutcomeMembersState = { [actionHash: ActionHashB64]: WithActionHash } @@ -28,9 +36,7 @@ const defaultState: OutcomeMembersState = {} export default function ( state: OutcomeMembersState = defaultState, - action: - | Action> - | Action + action: Action> | Action ): OutcomeMembersState { const { payload, type } = action diff --git a/web/src/redux/persistent/projects/outcomes/reducer.ts b/web/src/redux/persistent/projects/outcomes/reducer.ts index a4a090a4a..dcdba0105 100644 --- a/web/src/redux/persistent/projects/outcomes/reducer.ts +++ b/web/src/redux/persistent/projects/outcomes/reducer.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import { z } from 'zod' import { CREATE_OUTCOME, @@ -11,10 +12,28 @@ import { } from './actions' import { isCrud, crudReducer } from '../../crudRedux' import { FETCH_ENTRY_POINT_DETAILS } from '../entry-points/actions' -import { Action, CellIdString, ActionHashB64, WithActionHash } from '../../../../types/shared' -import { CreateOutcomeWithConnectionOutput, DeleteOutcomeFullyResponse, EntryPointDetails, Outcome, Scope, TimeFrame } from '../../../../types' +import { + Action, + CellIdString, + ActionHashB64, + WithActionHash, +} from '../../../../types/shared' +import { + CreateOutcomeWithConnectionOutput, + DeleteOutcomeFullyResponse, + EntryPointDetails, + Outcome, + OutcomeSchema, +} from '../../../../types' import { WireRecord } from '../../../../api/hdkCrud' +export const ProjectOutcomesStateSchema = z.record( + z + .object({ + actionHash: z.string(), + }) + .merge(OutcomeSchema) +) export type ProjectOutcomesState = { [actionHash: ActionHashB64]: WithActionHash } @@ -23,8 +42,22 @@ export type OutcomesState = { } const defaultState: OutcomesState = {} -export default function (state: OutcomesState = defaultState, action: OutcomesAction | Action | Action): OutcomesState { - if (isCrud(action, CREATE_OUTCOME, FETCH_OUTCOMES, UPDATE_OUTCOME, DELETE_OUTCOME)) { +export default function ( + state: OutcomesState = defaultState, + action: + | OutcomesAction + | Action + | Action +): OutcomesState { + if ( + isCrud( + action, + CREATE_OUTCOME, + FETCH_OUTCOMES, + UPDATE_OUTCOME, + DELETE_OUTCOME + ) + ) { const crudAction = action as Action> return crudReducer( state, diff --git a/web/src/redux/persistent/projects/tags/reducer.ts b/web/src/redux/persistent/projects/tags/reducer.ts index 5499d892d..48e64f9c4 100644 --- a/web/src/redux/persistent/projects/tags/reducer.ts +++ b/web/src/redux/persistent/projects/tags/reducer.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import { z } from 'zod' import { CREATE_TAG, FETCH_TAGS, UPDATE_TAG, DELETE_TAG } from './actions' import { isCrud, crudReducer } from '../../crudRedux' @@ -7,7 +8,14 @@ import { ActionHashB64, WithActionHash, } from '../../../../types/shared' -import { Tag } from '../../../../types' +import { Tag, TagSchema } from '../../../../types' + +export const ProjectTagsStateSchema = z.record( + z.object({ actionHash: z.string() }).merge(TagSchema) +) +export type ProjectTagsState = { + [actionHash: ActionHashB64]: WithActionHash +} type TagState = { [cellId: CellIdString]: { diff --git a/web/src/routes/App.component.tsx b/web/src/routes/App.component.tsx index f0443bbe0..4dff9f5ec 100644 --- a/web/src/routes/App.component.tsx +++ b/web/src/routes/App.component.tsx @@ -121,7 +121,8 @@ const App: React.FC = ({ ) // custom hooks const updateVersionInfo = useVersionChecker() - // set true to test + // to do development testing of migration-feature + // uncomment the following line (and comment the one above) // const updateVersionInfo = useVersionChecker(true) const finishMigrationChecker = useFinishMigrationChecker() const { fileDownloaded, setFileDownloaded } = useFileDownloaded() diff --git a/web/src/routes/App.connector.ts b/web/src/routes/App.connector.ts index 63eaa288d..f289e6106 100644 --- a/web/src/routes/App.connector.ts +++ b/web/src/routes/App.connector.ts @@ -74,7 +74,7 @@ function mapStateToProps(state: RootState): AppStateProps { const selectedLayeringAlgo = activeProjectMeta ? activeProjectMeta.layeringAlgorithm - : LayeringAlgorithm.LongestPath + : "LongestPath" return { profilesCellIdString, diff --git a/web/src/routes/Dashboard/Dashboard.connector.ts b/web/src/routes/Dashboard/Dashboard.connector.ts index 4a4ffeb67..94aca5b53 100644 --- a/web/src/routes/Dashboard/Dashboard.connector.ts +++ b/web/src/routes/Dashboard/Dashboard.connector.ts @@ -118,7 +118,7 @@ function mapDispatchToProps(dispatch): DashboardDispatchProps { creatorAgentPubKey: agentAddress, createdAt: Date.now(), isImported: false, - layeringAlgorithm: LayeringAlgorithm.CoffmanGraham, + layeringAlgorithm: "CoffmanGraham", topPriorityOutcomes: [], isMigrated: null, } diff --git a/web/src/routes/VersionUpdateLeaving/VersionUpdateLeaving.tsx b/web/src/routes/VersionUpdateLeaving/VersionUpdateLeaving.tsx index e14d19e00..50cffa46b 100644 --- a/web/src/routes/VersionUpdateLeaving/VersionUpdateLeaving.tsx +++ b/web/src/routes/VersionUpdateLeaving/VersionUpdateLeaving.tsx @@ -2,11 +2,10 @@ import React, { useEffect, useState } from 'react' import './VersionUpdateLeaving.scss' -import exportProjectsData, { - AllProjectsDataExport, -} from '../../migrating/export' +import exportProjectsData from '../../migrating/export' import { useStore } from 'react-redux' import MigrationProgress from '../../components/MigrationProgress/MigrationProgress' +import { AllProjectsDataExport } from 'zod-models' const checkIfExportNeeded = (currentVersion: string, toVersion: string) => { // compare @@ -51,6 +50,7 @@ export type VersionUpdateLeavingProps = { triggerAMigrationCheck: () => void updateVersionInfo?: { currentVersion: string + integrityVersion: number platform: string arch: string newReleaseVersion: string @@ -77,6 +77,7 @@ const VersionUpdateLeaving: React.FC = ({ const allExportData = await exportProjectsData( store, updateVersionInfo.newReleaseVersion, + updateVersionInfo.integrityVersion, (completed, toComplete) => { // percent // the + 1 is because there's an additional step after @@ -137,7 +138,13 @@ const VersionUpdateLeaving: React.FC = ({ updateVersionInfo.newReleaseVersion ) ) { - runExport().then(controlDownloadNextVersion) + runExport().then(controlDownloadNextVersion).catch((e) => { + console.error('error running export', e) + setTitle('An unexpected error occurred.') + const message = 'You may be able to recover by quitting and restarting Acorn. Please report this issue and provide the following error to support: ' + JSON.stringify(e) + // throw new Error(message) + setStatus(message) + }) } else { controlDownloadNextVersion() } diff --git a/web/src/stories/testData/testProjectMeta.ts b/web/src/stories/testData/testProjectMeta.ts index 19f4f3e82..a4e07a0ad 100644 --- a/web/src/stories/testData/testProjectMeta.ts +++ b/web/src/stories/testData/testProjectMeta.ts @@ -8,7 +8,7 @@ const testProject: WithActionHash = { image: null, passphrase: 'testPassphrase', isImported: false, - layeringAlgorithm: LayeringAlgorithm.CoffmanGraham, + layeringAlgorithm: "CoffmanGraham", topPriorityOutcomes: [], isMigrated: null, actionHash: 'testProjectActionHash', diff --git a/web/src/types/connection.ts b/web/src/types/connection.ts index f70ad4f5e..87b51a877 100644 --- a/web/src/types/connection.ts +++ b/web/src/types/connection.ts @@ -1,8 +1,3 @@ -import { ActionHashB64 } from './shared' +import { Connection as _Connection } from 'zod-models' -export interface Connection { - parentActionHash: ActionHashB64, - childActionHash: ActionHashB64, - randomizer: number, //i64, - isImported: boolean, -} \ No newline at end of file +export type Connection = _Connection diff --git a/web/src/types/entryPoint.ts b/web/src/types/entryPoint.ts index dfc0f3ed2..34439b41c 100644 --- a/web/src/types/entryPoint.ts +++ b/web/src/types/entryPoint.ts @@ -1,9 +1,3 @@ -import { AgentPubKeyB64, ActionHashB64 } from "./shared"; +import { EntryPoint as _EntryPoint } from 'zod-models' -export interface EntryPoint { - color: string, - creatorAgentPubKey: AgentPubKeyB64, - createdAt: number, //f64, - outcomeActionHash: ActionHashB64, - isImported: boolean, -} \ No newline at end of file +export type EntryPoint = _EntryPoint diff --git a/web/src/types/outcome.ts b/web/src/types/outcome.ts index 7d193a6bb..7b8c4b8c6 100644 --- a/web/src/types/outcome.ts +++ b/web/src/types/outcome.ts @@ -1,47 +1,31 @@ import { OutcomeComment } from './outcomeComment' import { OutcomeVote } from './outcomeVote' import { Profile } from './profile' -import { AgentPubKeyB64, Option, WithActionHash } from './shared' - -export interface Outcome { - content: string - creatorAgentPubKey: AgentPubKeyB64 - editorAgentPubKey: Option - timestampCreated: number //f64, - timestampUpdated: Option //f64 - scope: Scope - tags: Option> - description: string - isImported: boolean - githubLink: string -} +import { WithActionHash } from './shared' +import { + SmallTask as _SmallTask, + SmallScope as _SmallScope, + TimeFrame as _TimeFrame, + SmallsEstimate as _SmallsEstimate, + UncertainScope as _UncertainScope, + Scope as _Scope, + Outcome as _Outcome, +} from 'zod-models' export type AchievementStatus = 'Achieved' | 'NotAchieved' -export type SmallTask = { - complete: boolean - task: string -} -export interface SmallScope { - achievementStatus: AchievementStatus - targetDate: Option - taskList: Array -} -export interface TimeFrame { - fromDate: number //f64, - toDate: number //f64, -} -export type SmallsEstimate = number -export interface UncertainScope { - smallsEstimate: Option - timeFrame: Option - inBreakdown: boolean -} +export type SmallTask = _SmallTask +export type SmallScope = _SmallScope +export type SmallsEstimate = _SmallsEstimate +export type ScopeSmallVariant = SmallScope -export type ScopeSmallVariant = { Small: SmallScope } -export type ScopeUncertainVariant = { Uncertain: UncertainScope } -export type Scope = ScopeSmallVariant | ScopeUncertainVariant +export type TimeFrame = _TimeFrame +export type UncertainScope = _UncertainScope +export type ScopeUncertainVariant = UncertainScope +export type Scope = _Scope + +// TODO: convert to zod schema export type ComputedAchievementStatus = { uncertains: number smallsAchieved: number @@ -50,6 +34,8 @@ export type ComputedAchievementStatus = { tasksAchieved: number // only for Smalls, 0 the rest of the time guaranteed simple: ComputedSimpleAchievementStatus } + +export type Outcome = _Outcome /* Uncertain { diff --git a/web/src/types/outcomeComment.ts b/web/src/types/outcomeComment.ts index b8c4c9285..86e9b4f2b 100644 --- a/web/src/types/outcomeComment.ts +++ b/web/src/types/outcomeComment.ts @@ -1,9 +1,3 @@ -import { AgentPubKeyB64, ActionHashB64 } from "./shared"; +import { OutcomeComment as _OutcomeComment } from 'zod-models' -export interface OutcomeComment { - outcomeActionHash: ActionHashB64, - content: string, - creatorAgentPubKey: AgentPubKeyB64, - unixTimestamp: number, //f64, - isImported: boolean, -} +export type OutcomeComment = _OutcomeComment diff --git a/web/src/types/outcomeMember.ts b/web/src/types/outcomeMember.ts index 740d49a3d..c2ae17c53 100644 --- a/web/src/types/outcomeMember.ts +++ b/web/src/types/outcomeMember.ts @@ -1,11 +1,3 @@ -import { ActionHashB64, AgentPubKeyB64 } from "./shared"; +import { OutcomeMember as _OutcomeMember } from 'zod-models' -export interface OutcomeMember { - outcomeActionHash: ActionHashB64, - // the "assignee" - memberAgentPubKey: AgentPubKeyB64, - // the person who authored this entry - creatorAgentPubKey: AgentPubKeyB64, - unixTimestamp: number, //f64, - isImported: boolean, -} \ No newline at end of file +export type OutcomeMember = _OutcomeMember diff --git a/web/src/types/outcomeVote.ts b/web/src/types/outcomeVote.ts index ebd6cd8c9..66c08084f 100644 --- a/web/src/types/outcomeVote.ts +++ b/web/src/types/outcomeVote.ts @@ -1,12 +1,13 @@ -import { ActionHashB64, AgentPubKeyB64 } from "./shared" +import { ActionHashB64, AgentPubKeyB64 } from './shared' +// TODO: convert to zod schema export interface OutcomeVote { - outcomeActionHash: ActionHashB64, - urgency: number, //f64, - importance: number, //f64, - impact: number, //f64, - effort: number, //f64, - creatorAgentPubKey: AgentPubKeyB64, - unixTimestamp: number, //f64, - isImported: boolean, + outcomeActionHash: ActionHashB64 + urgency: number //f64, + importance: number //f64, + impact: number //f64, + effort: number //f64, + creatorAgentPubKey: AgentPubKeyB64 + unixTimestamp: number //f64, + isImported: boolean } diff --git a/web/src/types/profile.ts b/web/src/types/profile.ts index 712594203..6a3af36b1 100644 --- a/web/src/types/profile.ts +++ b/web/src/types/profile.ts @@ -1,16 +1,7 @@ -import { AgentPubKeyB64, ActionHashB64 } from './shared' +import { Profile as _Profile } from 'zod-models' +import { ActionHashB64 } from './shared' -type Status = "Online" | "Offline" | "Away" - -export interface Profile { - firstName: string, - lastName: string, - handle: string, - status: Status, - avatarUrl: string, - agentPubKey: AgentPubKeyB64, - isImported: boolean, -} +export type Profile = _Profile export type AssigneeWithActionHash = { profile: Profile diff --git a/web/src/types/projectMeta.ts b/web/src/types/projectMeta.ts index 48452e4fa..5b1f984c5 100644 --- a/web/src/types/projectMeta.ts +++ b/web/src/types/projectMeta.ts @@ -1,16 +1,11 @@ import { EntryPoint } from './entryPoint' import { Outcome } from './outcome' import { Profile } from './profile' -import { - AgentPubKeyB64, - ActionHashB64, - Option, - CellIdString, - WithActionHash, -} from './shared' +import { AgentPubKeyB64, CellIdString, WithActionHash } from './shared' +import { ProjectMetaV1 } from 'zod-models' export type ProjectAggregated = { - projectMeta: WithActionHash + projectMeta: WithActionHash cellId: CellIdString presentMembers: AgentPubKeyB64[] members: Profile[] @@ -19,20 +14,3 @@ export type ProjectAggregated = { outcome: WithActionHash }[] } - -export enum LayeringAlgorithm { - LongestPath = 'LongestPath', - CoffmanGraham = 'CoffmanGraham', -} - -export interface ProjectMeta { - creatorAgentPubKey: AgentPubKeyB64 - createdAt: number // f64 - name: string - image: Option - passphrase: string - isImported: boolean - layeringAlgorithm: LayeringAlgorithm - topPriorityOutcomes: Array - isMigrated: Option -} diff --git a/web/src/types/tag.ts b/web/src/types/tag.ts index 4001dee0e..e03c8764f 100644 --- a/web/src/types/tag.ts +++ b/web/src/types/tag.ts @@ -1,4 +1,3 @@ -export interface Tag { - backgroundColor: string - text: string -} +import { Tag as _Tag } from 'zod-models' + +export type Tag = _Tag diff --git a/web/src/utils.ts b/web/src/utils.ts index 9b2b2386a..9280cea9d 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -1,6 +1,6 @@ import { HoloHash, CellId } from '@holochain/client' -// import BufferAll from 'buffer/' -// const Buffer = BufferAll.Buffer +import BufferAll from 'buffer/' +const Buffer = BufferAll.Buffer export function hashToString(hash: HoloHash) { // nodejs diff --git a/web/test/import.test.ts b/web/test/import.test.ts index 1b4d05e4c..068b8288c 100644 --- a/web/test/import.test.ts +++ b/web/test/import.test.ts @@ -2,7 +2,13 @@ import importProjectsData, { internalImportProjectsData, } from '../src/migrating/import/import' import { sampleGoodDataExport } from './sample-good-data-export' -import { OutcomeMember, ProjectMeta, Tag } from '../src/types' +import { + Connection, + Outcome, + OutcomeMember, + ProjectMeta, + Tag, +} from '../src/types' import { WireRecord } from '../src/api/hdkCrud' import mockUnmigratedProjectMeta from './mockProjectMeta' import mockBaseRootState from './mockRootState' @@ -34,6 +40,7 @@ import { cloneOutcome as _cloneOutcome, cloneTag as _cloneTag, cloneData as _cloneData, + cloneProjectMeta as _cloneProjectMeta, ActionHashMap, } from '../src/migrating/import/cloneFunctions' import { @@ -47,6 +54,8 @@ import { import mockWhoami from './mockWhoami' import { cellIdFromString } from '../src/utils' import mockActionHashMaps from './mockActionHashMaps' +import { WithActionHash } from '../src/types/shared' +import { ZodError } from 'zod' import { finalizeCreateProject as _finalizeCreateProject } from '../src/projects/createProject' let store: any // too complex of a type to mock @@ -226,8 +235,8 @@ describe('importProjectsData()', () => { onStep ) } catch (e) { - expect(e).toBeInstanceOf(SyntaxError) - expect(e.message).toBe('Unexpected token i in JSON at position 0') + expect(e).toBeInstanceOf(ZodError) + expect(e.errors[0].message).toBe('Invalid JSON') } mockMigrationData = null @@ -241,10 +250,44 @@ describe('importProjectsData()', () => { onStep ) } catch (e) { - expect(e).toBeInstanceOf(TypeError) - expect(e.message).toBe( - "Cannot read properties of null (reading 'projects')" + expect(e).toBeInstanceOf(ZodError) + expect(e.errors[0].message).toBe('Expected string, received null') + } + + mockMigrationData = '{"foo": "bar"}' + try { + await internalImportProjectsData( + profilesZomeApi, + installProjectAndImport, + installProject, + store, + mockMigrationData, + onStep ) + } catch (e) { + expect(e.errors).toEqual([ + { + code: 'invalid_type', + expected: 'object', + received: 'undefined', + path: ['myProfile'], + message: 'Required', + }, + { + code: 'invalid_type', + expected: 'array', + received: 'undefined', + path: ['projects'], + message: 'Required', + }, + { + code: 'invalid_type', + expected: 'number', + received: 'undefined', + path: ['integrityVersion'], + message: 'Required', + }, + ]) } }) }) @@ -393,7 +436,9 @@ describe('cloneDataSet()', () => { expect(Object.keys(projectData.tags).length).toBe(1) expect(Object.keys(result).length).toBe(1) - const oldTagActionHash = Object.values(projectData.tags)[0].actionHash + const oldTagActionHash = Object.values>( + projectData.tags + )[0].actionHash expect(result).toEqual({ [oldTagActionHash]: 'testActionHash' }) }) }) @@ -410,12 +455,11 @@ describe('cloneTag()', () => { describe('cloneOutcome()', () => { it('creates a deep copy of the old outcome', () => { - const oldOutcome = Object.values( + const oldOutcome = Object.values>( sampleGoodDataExport.projects[0].outcomes )[0] const tagActionHashMap = { - [sampleGoodDataExport.projects[0].tags.testTagActionHash.actionHash]: - 'testActionHash', + '124': 'testActionHash', } const result = _cloneOutcome(tagActionHashMap)(oldOutcome) @@ -429,20 +473,58 @@ describe('cloneOutcome()', () => { describe('cloneConnection()', () => { it('creates a deep copy of the old connection', () => { - const oldConnection = Object.values( + const oldConnection = Object.values>( sampleGoodDataExport.projects[0].connections )[0] const outcome1 = { [Object.keys( sampleGoodDataExport.projects[0].outcomes - )[0]]: Object.values(sampleGoodDataExport.projects[0].outcomes)[0] - .actionHash, + )[0]]: Object.values>( + sampleGoodDataExport.projects[0].outcomes + )[0].actionHash, } const outcome2 = { [Object.keys( sampleGoodDataExport.projects[0].outcomes - )[1]]: Object.values(sampleGoodDataExport.projects[0].outcomes)[1] - .actionHash, + )[1]]: Object.values>( + sampleGoodDataExport.projects[0].outcomes + )[1].actionHash, + } + const outcomeActionHashMap: ActionHashMap = { + ...outcome1, + ...outcome2, + } + const result = _cloneConnection(outcomeActionHashMap)(oldConnection) + + expect(result).toEqual({ + ...oldConnection, + parentActionHash: oldConnection.parentActionHash, + childActionHash: oldConnection.childActionHash, + randomizer: Number(oldConnection.randomizer.toFixed()), + isImported: true, + }) + }) + + it('still clones when given a float randomizer value', () => { + const oldConnection = { + ...Object.values>( + sampleGoodDataExport.projects[0].connections + )[0], + randomizer: 0.5, + } + const outcome1 = { + [Object.keys( + sampleGoodDataExport.projects[0].outcomes + )[0]]: Object.values>( + sampleGoodDataExport.projects[0].outcomes + )[0].actionHash, + } + const outcome2 = { + [Object.keys( + sampleGoodDataExport.projects[0].outcomes + )[1]]: Object.values>( + sampleGoodDataExport.projects[0].outcomes + )[1].actionHash, } const outcomeActionHashMap: ActionHashMap = { ...outcome1, @@ -463,11 +545,11 @@ describe('cloneData()', () => { it('creates a deep copy of the old data', () => { // using outcome member as a generic example - const oldData = Object.values( + const oldData = Object.values>( sampleGoodDataExport.projects[0].outcomeMembers )[0] const outcomeActionHashMap: ActionHashMap = { - [oldData.outcomeActionHash]: Object.values( + [oldData.outcomeActionHash]: Object.values>( sampleGoodDataExport.projects[0].outcomes )[2].actionHash, } @@ -481,3 +563,40 @@ describe('cloneData()', () => { }) }) }) + +describe('cloneProjectMeta()', () => { + it('creates a deep copy of the old project meta', () => { + const oldData = { ...sampleGoodDataExport.projects[0].projectMeta } + const outcomeActionHashMap: ActionHashMap = { + oldActionHash: 'newActionHash', + } + + const result = _cloneProjectMeta( + outcomeActionHashMap, + oldData.creatorAgentPubKey, + oldData.passphrase + )(oldData) + + expect(result.topPriorityOutcomes).toEqual(['newActionHash']) + expect(result.layeringAlgorithm).toEqual(oldData['layeringAlgorithm']) + expect(result.createdAt).not.toEqual(oldData.createdAt) + }) + + it('when topPriorityOutcomes and layeringAlgorithm are missing, it adds default values', () => { + const oldData = { ...sampleGoodDataExport.projects[0].projectMeta } + const outcomeActionHashMap: ActionHashMap = { + oldActionHash: 'newActionHash', + } + delete oldData['topPriorityOutcomes'] + delete oldData['layeringAlgorithm'] + + const result = _cloneProjectMeta( + outcomeActionHashMap, + oldData.creatorAgentPubKey, + oldData.passphrase + )(oldData) + + expect(result.topPriorityOutcomes).toEqual([]) + expect(result.layeringAlgorithm).toEqual("LongestPath") + }) +}) diff --git a/web/test/mockProjectData.ts b/web/test/mockProjectData.ts index f9df48fef..1bbab8fc4 100644 --- a/web/test/mockProjectData.ts +++ b/web/test/mockProjectData.ts @@ -9,7 +9,7 @@ const mockProjectData: ProjectExportDataV1 = { image: 'testProjectImage', passphrase: 'testPassphrase', isImported: false, - layeringAlgorithm: LayeringAlgorithm.CoffmanGraham, + layeringAlgorithm: "CoffmanGraham", topPriorityOutcomes: [], isMigrated: null, actionHash: 'testProjectActionHash', diff --git a/web/test/mockProjectMeta.ts b/web/test/mockProjectMeta.ts index f300fabdf..ea58e9600 100644 --- a/web/test/mockProjectMeta.ts +++ b/web/test/mockProjectMeta.ts @@ -8,7 +8,7 @@ const projectMeta: ProjectMeta = { image: null, passphrase: 'testPassphrase', isImported: false, - layeringAlgorithm: LayeringAlgorithm.CoffmanGraham, + layeringAlgorithm: "CoffmanGraham", topPriorityOutcomes: [], isMigrated: null, } diff --git a/web/test/sample-good-data-export.ts b/web/test/sample-good-data-export.ts index 375d13dde..ba4bd4285 100644 --- a/web/test/sample-good-data-export.ts +++ b/web/test/sample-good-data-export.ts @@ -1,13 +1,9 @@ -import { - AllProjectsDataExport, - ProjectExportDataV1, -} from '../src/migrating/export' +import { AllProjectsDataExport, ProjectExportDataV1 } from 'zod-models' import testComments from '../src/stories/testData/testComments' import testEntryPoint from '../src/stories/testData/testEntryPoint' import testOutcomeMember from '../src/stories/testData/testOutcomeMember' import testProfile from '../src/stories/testData/testProfile' import testTags from '../src/stories/testData/testTags' -import { LayeringAlgorithm } from '../src/types' const sampleGoodUnmigratedProjectData: ProjectExportDataV1 = { projectMeta: { @@ -17,8 +13,8 @@ const sampleGoodUnmigratedProjectData: ProjectExportDataV1 = { image: '', passphrase: 'daily plant employee shorten define', isImported: false, - layeringAlgorithm: LayeringAlgorithm.CoffmanGraham, - topPriorityOutcomes: [], + layeringAlgorithm: "CoffmanGraham", + topPriorityOutcomes: ['oldActionHash'], isMigrated: null, actionHash: 'uhCkkBzwPwj4l3XGeXJTt9mxL88LOKOm_fvh0kw6PSf5jnP_RUG14', }, @@ -219,7 +215,7 @@ const sampleGoodMigratedProjectData: ProjectExportDataV1 = { const sampleGoodDataExport: AllProjectsDataExport = { myProfile: testProfile, projects: [sampleGoodUnmigratedProjectData, sampleGoodMigratedProjectData], - integrityVersion: '8', + integrityVersion: 8, } export { sampleGoodDataExport } diff --git a/zod-models/package-lock.json b/zod-models/package-lock.json new file mode 100644 index 000000000..105ed82b1 --- /dev/null +++ b/zod-models/package-lock.json @@ -0,0 +1,40 @@ +{ + "name": "zod-models", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zod-models", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "zod": "^3.21.4" + }, + "devDependencies": { + "typescript": "^5.1.6" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/zod-models/package.json b/zod-models/package.json new file mode 100644 index 000000000..5a108bfc9 --- /dev/null +++ b/zod-models/package.json @@ -0,0 +1,18 @@ +{ + "name": "zod-models", + "version": "1.0.0", + "description": "", + "main": "dist/zodModels.js", + "scripts": { + "build": "npx tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^5.1.6" + }, + "dependencies": { + "zod": "^3.21.4" + } +} diff --git a/zod-models/src/allProjectsDataExport/allProjectsDataExportSchema.ts b/zod-models/src/allProjectsDataExport/allProjectsDataExportSchema.ts new file mode 100644 index 000000000..e40643a23 --- /dev/null +++ b/zod-models/src/allProjectsDataExport/allProjectsDataExportSchema.ts @@ -0,0 +1,20 @@ +import {z} from 'zod' +import ProfileSchema from '../profile/profileSchema' +import ProjectExportDataV1Schema from '../projectExportData/v1/projectExportDataV1Schema' + +export const BackwardsCompatibleAllProjectsExportSchema = z.object({ + myProfile: ProfileSchema, + projects: z.array(ProjectExportDataV1Schema), +}) + +export type BackwardsCompatibleAllProjectsExport = z.infer + +export const AllProjectsDataExportSchema = z.object({ + myProfile: ProfileSchema, + projects: z.array(ProjectExportDataV1Schema), + integrityVersion: z.number(), +}) + +export type AllProjectsDataExport = z.infer + +export default AllProjectsDataExportSchema \ No newline at end of file diff --git a/zod-models/src/connection/connectionSchema.ts b/zod-models/src/connection/connectionSchema.ts new file mode 100644 index 000000000..317ce43b4 --- /dev/null +++ b/zod-models/src/connection/connectionSchema.ts @@ -0,0 +1,17 @@ +import {z} from 'zod' + +const ConnectionSchema = z.object({ + parentActionHash: z.string(), + childActionHash: z.string(), + randomizer: z.number(), // needs to be broad enough to allow floats for backwards compatibility, but is now an int + isImported: z.boolean(), +}) + +export const ProjectConnectionsStateSchema = z.record( + z.object({ actionHash: z.string() }).merge(ConnectionSchema) +) + +export type ProjectConnectionsState = z.infer +export type Connection = z.infer + +export default ConnectionSchema \ No newline at end of file diff --git a/zod-models/src/entryPoint/entryPointSchema.ts b/zod-models/src/entryPoint/entryPointSchema.ts new file mode 100644 index 000000000..2ef8e7de0 --- /dev/null +++ b/zod-models/src/entryPoint/entryPointSchema.ts @@ -0,0 +1,22 @@ +import {z} from 'zod' + +const EntryPointSchema = z.object({ + color: z.string(), + creatorAgentPubKey: z.string(), + createdAt: z.number(), + outcomeActionHash: z.string(), + isImported: z.boolean(), +}) + +export const ProjectEntryPointsStateSchema = z.record( + z + .object({ + actionHash: z.string(), + }) + .merge(EntryPointSchema) +) + +export type EntryPoint = z.infer +export type ProjectEntryPointsState = z.infer + +export default EntryPointSchema \ No newline at end of file diff --git a/zod-models/src/outcome/outcomeSchema.ts b/zod-models/src/outcome/outcomeSchema.ts new file mode 100644 index 000000000..cfadbee7e --- /dev/null +++ b/zod-models/src/outcome/outcomeSchema.ts @@ -0,0 +1,25 @@ +import {z} from 'zod' +import ScopeSchema from '../scope/scopeSchema' +import WithActionHashSchema from '../withActionHashSchema' + +const OutcomeSchema = z.object({ + content: z.string(), + creatorAgentPubKey: z.string(), + editorAgentPubKey: z.string().nullable(), + timestampCreated: z.number().gt(0), + timestampUpdated: z.number().gt(0).nullable(), + scope: ScopeSchema, + tags: z.array(z.string()).nullable(), + description: z.string(), + isImported: z.boolean(), + githubLink: z.string().url().or(z.literal('')), +}) + +export const ProjectOutcomesStateSchema = z.record( + WithActionHashSchema.merge(OutcomeSchema) +) +export type ProjectOutcomesState = z.infer + + +export type Outcome = z.infer +export default OutcomeSchema \ No newline at end of file diff --git a/zod-models/src/outcomeComment/outcomeCommentSchema.ts b/zod-models/src/outcomeComment/outcomeCommentSchema.ts new file mode 100644 index 000000000..9872a0eae --- /dev/null +++ b/zod-models/src/outcomeComment/outcomeCommentSchema.ts @@ -0,0 +1,23 @@ +import {z} from 'zod' + +export const OutcomeCommentSchema = z.object({ + outcomeActionHash: z.string(), + content: z.string(), + creatorAgentPubKey: z.string(), + unixTimestamp: z.number(), + isImported: z.boolean(), +}) + +export const ProjectOutcomeCommentsStateSchema = z.record( + z + .object({ + actionHash: z.string(), + }) + .merge(OutcomeCommentSchema) +) + + +export type OutcomeComment = z.infer +export type ProjectOutcomeCommentsState = z.infer + +export default OutcomeCommentSchema \ No newline at end of file diff --git a/zod-models/src/outcomeMember/outcomeMemberSchema.ts b/zod-models/src/outcomeMember/outcomeMemberSchema.ts new file mode 100644 index 000000000..547639fd3 --- /dev/null +++ b/zod-models/src/outcomeMember/outcomeMemberSchema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' + +export const OutcomeMemberSchema = z.object({ + outcomeActionHash: z.string(), + memberAgentPubKey: z.string(), + creatorAgentPubKey: z.string(), + unixTimestamp: z.number(), + isImported: z.boolean(), +}) + +export const ProjectOutcomeMembersStateSchema = z.record( + z.object({ actionHash: z.string() }).merge(OutcomeMemberSchema) +) + +export type ProjectOutcomeMembersState = z.infer +export type OutcomeMember = z.infer + +export default OutcomeMemberSchema \ No newline at end of file diff --git a/zod-models/src/profile/profileSchema.ts b/zod-models/src/profile/profileSchema.ts new file mode 100644 index 000000000..ec417d98e --- /dev/null +++ b/zod-models/src/profile/profileSchema.ts @@ -0,0 +1,14 @@ +import {z} from 'zod' + +const ProfileSchema = z.object({ + firstName: z.string(), + lastName: z.string(), + handle: z.string(), + status: z.enum(['Online', 'Offline', 'Away']), + avatarUrl: z.string(), + agentPubKey: z.string(), + isImported: z.boolean(), +}) + +export type Profile = z.infer +export default ProfileSchema \ No newline at end of file diff --git a/zod-models/src/projectExportData/v1/projectExportDataV1Schema.ts b/zod-models/src/projectExportData/v1/projectExportDataV1Schema.ts new file mode 100644 index 000000000..0f6e97246 --- /dev/null +++ b/zod-models/src/projectExportData/v1/projectExportDataV1Schema.ts @@ -0,0 +1,36 @@ +import {z} from 'zod' +import { ProjectConnectionsStateSchema } from '../../connection/connectionSchema' +import { ProjectEntryPointsStateSchema } from '../../entryPoint/entryPointSchema' +import { ProjectOutcomesStateSchema } from '../../outcome/outcomeSchema' +import { ProjectOutcomeCommentsStateSchema } from '../../outcomeComment/outcomeCommentSchema' +import { ProjectOutcomeMembersStateSchema } from '../../outcomeMember/outcomeMemberSchema' +import BackwardsCompatibleProjectMetaSchema, { ProjectMetaV1WithActionHashSchema } from '../../projectMetaSchema/projectMetaSchema' +import { ProjectTagsStateSchema } from '../../tag/tagSchema' + +const BackwardsCompatibleProjectExportSchema = z.object({ + // projectMeta requires backwards compatibility + projectMeta: BackwardsCompatibleProjectMetaSchema, + outcomes: ProjectOutcomesStateSchema, + connections: ProjectConnectionsStateSchema, + outcomeMembers: ProjectOutcomeMembersStateSchema, + outcomeComments: ProjectOutcomeCommentsStateSchema, + entryPoints: ProjectEntryPointsStateSchema, + tags: ProjectTagsStateSchema, +}) + +const ProjectExportDataV1Schema = z.object({ + // projectMeta has a v1 specific schema + projectMeta: ProjectMetaV1WithActionHashSchema, + outcomes: ProjectOutcomesStateSchema, + connections: ProjectConnectionsStateSchema, + outcomeMembers: ProjectOutcomeMembersStateSchema, + outcomeComments: ProjectOutcomeCommentsStateSchema, + entryPoints: ProjectEntryPointsStateSchema, + tags: ProjectTagsStateSchema, +}) + +export type BackwardsCompatibleProjectExport = z.infer + +export type ProjectExportDataV1 = z.infer + +export default ProjectExportDataV1Schema \ No newline at end of file diff --git a/zod-models/src/projectMetaSchema/projectMetaSchema.ts b/zod-models/src/projectMetaSchema/projectMetaSchema.ts new file mode 100644 index 000000000..19d162d24 --- /dev/null +++ b/zod-models/src/projectMetaSchema/projectMetaSchema.ts @@ -0,0 +1,18 @@ + +import {z} from 'zod' +import ProjectMetaV0WithActionHashSchema from './v0/projectMetaV0Schema' +import ProjectMetaV1WithActionHashSchema, { ProjectMetaV1, ProjectMetaV1Schema } from './v1/projectMetaV1Schema' + +const BackwardsCompatibleProjectMetaSchema = z.union([ProjectMetaV1WithActionHashSchema, ProjectMetaV0WithActionHashSchema]) + +export { + ProjectMetaV0WithActionHashSchema, + ProjectMetaV1WithActionHashSchema, + ProjectMetaV1, + ProjectMetaV1Schema, +} +export type ProjectMetaV0WithActionHash = z.infer +export type ProjectMetaV1WithActionHash = z.infer + +export type BackwardsCompatibleProjectMeta = z.infer +export default BackwardsCompatibleProjectMetaSchema \ No newline at end of file diff --git a/zod-models/src/projectMetaSchema/v0/projectMetaV0Schema.ts b/zod-models/src/projectMetaSchema/v0/projectMetaV0Schema.ts new file mode 100644 index 000000000..ea0404d93 --- /dev/null +++ b/zod-models/src/projectMetaSchema/v0/projectMetaV0Schema.ts @@ -0,0 +1,16 @@ +import {z} from 'zod' +import WithActionHashSchema from '../../withActionHashSchema' + +export const ProjectMetaV0Schema = z.object({ + creatorAgentPubKey: z.string(), + createdAt: z.number(), + name: z.string(), + image: z.string().nullable(), + passphrase: z.string(), + isImported: z.boolean(), + isMigrated: z.string().nullable(), +}) + +const ProjectMetaV0WithActionHashSchema = WithActionHashSchema.merge(ProjectMetaV0Schema) + +export default ProjectMetaV0WithActionHashSchema \ No newline at end of file diff --git a/zod-models/src/projectMetaSchema/v1/projectMetaV1Schema.ts b/zod-models/src/projectMetaSchema/v1/projectMetaV1Schema.ts new file mode 100644 index 000000000..7d8c6e4dc --- /dev/null +++ b/zod-models/src/projectMetaSchema/v1/projectMetaV1Schema.ts @@ -0,0 +1,13 @@ +import {z} from 'zod' +import { ProjectMetaV0Schema } from '../v0/projectMetaV0Schema' +import WithActionHashSchema from '../../withActionHashSchema' + +export const ProjectMetaV1Schema = z.object({ + layeringAlgorithm: z.enum(['LongestPath', 'CoffmanGraham']), + topPriorityOutcomes: z.array(z.string()) +}).merge(ProjectMetaV0Schema) + +export type ProjectMetaV1 = z.infer + +const ProjectMetaV1WithActionHashSchema = WithActionHashSchema.merge(ProjectMetaV1Schema) +export default ProjectMetaV1WithActionHashSchema \ No newline at end of file diff --git a/zod-models/src/scope/scopeSchema.ts b/zod-models/src/scope/scopeSchema.ts new file mode 100644 index 000000000..6434bb8c7 --- /dev/null +++ b/zod-models/src/scope/scopeSchema.ts @@ -0,0 +1,8 @@ +import {z} from 'zod' +import SmallScopeSchema from './small/smallScopeSchema' +import UncertainScopeSchema from './uncertain/uncertainScopeSchema' + +const ScopeSchema = z.union([SmallScopeSchema, UncertainScopeSchema]) + +export type Scope = z.infer +export default ScopeSchema \ No newline at end of file diff --git a/zod-models/src/scope/small/smallScopeSchema.ts b/zod-models/src/scope/small/smallScopeSchema.ts new file mode 100644 index 000000000..01ad44ada --- /dev/null +++ b/zod-models/src/scope/small/smallScopeSchema.ts @@ -0,0 +1,14 @@ + +import {z} from 'zod' +import SmallTaskSchema from './smallTaskSchema' + +const SmallScopeSchema = z.object({ + Small: z.object({ + achievementStatus: z.enum(['Achieved', 'NotAchieved']), // TODO: extract into a reusable enum + targetDate: z.number().nullable(), + taskList: z.array(SmallTaskSchema), + }) +}) + +export type SmallScope = z.infer +export default SmallScopeSchema \ No newline at end of file diff --git a/zod-models/src/scope/small/smallTaskSchema.ts b/zod-models/src/scope/small/smallTaskSchema.ts new file mode 100644 index 000000000..4617c5588 --- /dev/null +++ b/zod-models/src/scope/small/smallTaskSchema.ts @@ -0,0 +1,10 @@ +import {z} from 'zod' + +const SmallTaskSchema = z.object({ + complete: z.boolean(), + task: z.string(), +}) + +export type SmallTask = z.infer + +export default SmallTaskSchema \ No newline at end of file diff --git a/zod-models/src/scope/small/smallsEstimateSchema.ts b/zod-models/src/scope/small/smallsEstimateSchema.ts new file mode 100644 index 000000000..346cc58b8 --- /dev/null +++ b/zod-models/src/scope/small/smallsEstimateSchema.ts @@ -0,0 +1,6 @@ +import {z} from 'zod' + +const SmallsEstimateSchema = z.number().nullable() + +export type SmallsEstimate = z.infer +export default SmallsEstimateSchema \ No newline at end of file diff --git a/zod-models/src/scope/uncertain/timeFrameSchema.ts b/zod-models/src/scope/uncertain/timeFrameSchema.ts new file mode 100644 index 000000000..c6ffb0c81 --- /dev/null +++ b/zod-models/src/scope/uncertain/timeFrameSchema.ts @@ -0,0 +1,9 @@ +import {z} from 'zod' + +const TimeFrameSchema = z.object({ + fromDate: z.number(), + toDate: z.number(), +}).nullable() + +export type TimeFrame = z.infer +export default TimeFrameSchema \ No newline at end of file diff --git a/zod-models/src/scope/uncertain/uncertainScopeSchema.ts b/zod-models/src/scope/uncertain/uncertainScopeSchema.ts new file mode 100644 index 000000000..8f28bb8e7 --- /dev/null +++ b/zod-models/src/scope/uncertain/uncertainScopeSchema.ts @@ -0,0 +1,14 @@ +import {z} from 'zod' +import SmallsEstimateSchema from '../small/smallsEstimateSchema' +import TimeFrameSchema from './timeFrameSchema' + +const UncertainScopeSchema = z.object({ + Uncertain: z.object({ + smallsEstimate: SmallsEstimateSchema, + timeFrame: TimeFrameSchema, + inBreakdown: z.boolean(), + }) +}) + +export type UncertainScope = z.infer +export default UncertainScopeSchema \ No newline at end of file diff --git a/zod-models/src/tag/tagSchema.ts b/zod-models/src/tag/tagSchema.ts new file mode 100644 index 000000000..dcca90e30 --- /dev/null +++ b/zod-models/src/tag/tagSchema.ts @@ -0,0 +1,15 @@ +import {z} from 'zod' + +export const TagSchema = z.object({ + backgroundColor: z.string(), + text: z.string(), +}) + +export const ProjectTagsStateSchema = z.record( + z.object({ actionHash: z.string() }).merge(TagSchema) +) + +export type Tag = z.infer +export type ProjectTagsState = z.infer + +export default TagSchema \ No newline at end of file diff --git a/zod-models/src/withActionHashSchema.ts b/zod-models/src/withActionHashSchema.ts new file mode 100644 index 000000000..5e434b8e6 --- /dev/null +++ b/zod-models/src/withActionHashSchema.ts @@ -0,0 +1,7 @@ +import {z} from 'zod' + +const WithActionHashSchema = z.object({ + actionHash: z.string().nonempty() +}) + +export default WithActionHashSchema \ No newline at end of file diff --git a/zod-models/src/zodModels.ts b/zod-models/src/zodModels.ts new file mode 100644 index 000000000..b9dbbf036 --- /dev/null +++ b/zod-models/src/zodModels.ts @@ -0,0 +1,113 @@ +import AllProjectsDataExportSchema, { + AllProjectsDataExport, + BackwardsCompatibleAllProjectsExportSchema, + BackwardsCompatibleAllProjectsExport, +} from "./allProjectsDataExport/allProjectsDataExportSchema"; +import ConnectionSchema, { + Connection, + ProjectConnectionsState, + ProjectConnectionsStateSchema, +} from "./connection/connectionSchema"; +import EntryPointSchema, { + EntryPoint, + ProjectEntryPointsState, + ProjectEntryPointsStateSchema, +} from "./entryPoint/entryPointSchema"; +import OutcomeCommentSchema, { + OutcomeComment, + ProjectOutcomeCommentsState, + ProjectOutcomeCommentsStateSchema, +} from "./outcomeComment/outcomeCommentSchema"; +import OutcomeMemberSchema, { + OutcomeMember, + ProjectOutcomeMembersState, + ProjectOutcomeMembersStateSchema, +} from "./outcomeMember/outcomeMemberSchema"; +import OutcomeSchema, { + Outcome, + ProjectOutcomesState, + ProjectOutcomesStateSchema, +} from "./outcome/outcomeSchema"; +import ProfileSchema, { Profile } from "./profile/profileSchema"; +import TagSchema, { + ProjectTagsState, + ProjectTagsStateSchema, + Tag, +} from "./tag/tagSchema"; +import BackwardsCompatibleProjectMetaSchema, { + ProjectMetaV0WithActionHashSchema, + ProjectMetaV1WithActionHashSchema, + ProjectMetaV0WithActionHash, + ProjectMetaV1WithActionHash, + BackwardsCompatibleProjectMeta, + ProjectMetaV1, + ProjectMetaV1Schema, +} from "./projectMetaSchema/projectMetaSchema"; +import ScopeSchema, { Scope } from "./scope/scopeSchema"; +import SmallScopeSchema, { SmallScope } from "./scope/small/smallScopeSchema"; +import SmallsEstimateSchema, { + SmallsEstimate, +} from "./scope/small/smallsEstimateSchema"; +import SmallTaskSchema, { SmallTask } from "./scope/small/smallTaskSchema"; +import TimeFrameSchema, { TimeFrame } from "./scope/uncertain/timeFrameSchema"; +import UncertainScopeSchema, { + UncertainScope, +} from "./scope/uncertain/uncertainScopeSchema"; +import ProjectExportDataV1Schema, { + ProjectExportDataV1, +} from "./projectExportData/v1/projectExportDataV1Schema"; + +export { + BackwardsCompatibleAllProjectsExportSchema, + BackwardsCompatibleAllProjectsExport, + AllProjectsDataExportSchema, + AllProjectsDataExport, + ProjectExportDataV1Schema, + ProjectExportDataV1, + ConnectionSchema, + Connection, + ProjectConnectionsState, + ProjectConnectionsStateSchema, + EntryPointSchema, + EntryPoint, + ProjectEntryPointsState, + ProjectEntryPointsStateSchema, + OutcomeCommentSchema, + OutcomeComment, + ProjectOutcomeCommentsState, + ProjectOutcomeCommentsStateSchema, + OutcomeMemberSchema, + OutcomeMember, + ProjectOutcomeMembersState, + ProjectOutcomeMembersStateSchema, + OutcomeSchema, + Outcome, + ProjectOutcomesState, + ProjectOutcomesStateSchema, + ProfileSchema, + Profile, + TagSchema, + ProjectTagsState, + ProjectTagsStateSchema, + Tag, + BackwardsCompatibleProjectMetaSchema, + BackwardsCompatibleProjectMeta, + ProjectMetaV0WithActionHashSchema, + ProjectMetaV1WithActionHashSchema, + ProjectMetaV0WithActionHash, + ProjectMetaV1WithActionHash, + ProjectMetaV1, + ProjectMetaV1Schema, + ScopeSchema, + Scope, + SmallScopeSchema, + SmallScope, + SmallsEstimateSchema, + SmallsEstimate, + SmallTaskSchema, + SmallTask, + TimeFrameSchema, + TimeFrame, + UncertainScopeSchema, + UncertainScope, +}; diff --git a/zod-models/tsconfig.json b/zod-models/tsconfig.json new file mode 100644 index 000000000..49ffcbab7 --- /dev/null +++ b/zod-models/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "./dist/", + "declaration": true, + "checkJs": false, + "sourceMap": true, + "noImplicitAny": false, + "module": "ES6", + "target": "ES6", + "jsx": "react", + "allowJs": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true + } + // "exclude": ["src/stories"] +}