From aa298208d5e3461410a596c6a4ea4df6b5198a2f Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 22 Aug 2023 15:19:58 -0400 Subject: [PATCH] initial implementation of listProjects --- src/mapeo-manager.js | 75 +++++++++++++++++++++++++++++++++++++-- src/mapeo-project.js | 15 ++++++++ src/types.ts | 6 ++++ test-e2e/manager-basic.js | 30 +++++++++------- 4 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/mapeo-manager.js b/src/mapeo-manager.js index d928d9bf5..8f34764eb 100644 --- a/src/mapeo-manager.js +++ b/src/mapeo-manager.js @@ -10,6 +10,9 @@ import { MapeoProject } from './mapeo-project.js' import { projectKeysTable, projectTable } from './schema/client.js' import { ProjectKeys } from './generated/keys.js' +/** @typedef {import("@mapeo/schema").ProjectValue} ProjectValue */ +/** @typedef {import("./types.js").ProjectInfo} ProjectInfo */ + const CLIENT_SQLITE_FILE_NAME = 'client.db' export class MapeoManager { @@ -43,9 +46,20 @@ export class MapeoManager { this.#activeProjects = new Map() } + /** + * @param {Buffer} keysCipher + * @param {string} projectId + * @returns {ProjectKeys} + */ + #decodeProjectKeysCipher(keysCipher, projectId) { + return ProjectKeys.decode( + this.#keyManager.decryptLocalMessage(keysCipher, projectId) + ) + } + /** * Create a new project. - * @param {import('type-fest').Simplify>>} [settings] + * @param {import('type-fest').Simplify>>} [settings] * @returns {Promise} */ async createProject(settings = {}) { @@ -131,8 +145,9 @@ export class MapeoManager { ) } - const projectKeys = ProjectKeys.decode( - this.#keyManager.decryptLocalMessage(result.keysCipher, projectId) + const projectKeys = this.#decodeProjectKeysCipher( + result.keysCipher, + projectId ) const project = new MapeoProject({ @@ -145,4 +160,58 @@ export class MapeoManager { return project } + + /** + * @returns {Promise>>} + */ + async listProjects() { + const allProjectsResult = this.#db + .select({ + projectId: projectKeysTable.projectId, + keysCipher: projectKeysTable.keysCipher, + }) + .from(projectKeysTable) + .all() + + if (allProjectsResult.length === 0) { + return [] + } + + /** @type {Array>>} */ + const resultPromises = [] + + for (const { projectId, keysCipher } of allProjectsResult) { + let project = this.#activeProjects.get(projectId) + + if (!project) { + const projectKeys = this.#decodeProjectKeysCipher(keysCipher, projectId) + + project = new MapeoProject({ + ...projectKeys, + storagePath: this.#storagePath, + keyManager: this.#keyManager, + sharedDb: this.#db, + sharedIndexWriter: this.#projectSettingsIndexWriter, + }) + + this.#activeProjects.set(projectId, project) + } + + const info = await project.$getProjectInfo() + const settings = await project.$getProjectSettings() + + resultPromises.push( + Promise.resolve({ + ...info, + name: settings.name, + }) + ) + + // TODO: Close project instance + // https://github.com/digidem/mapeo-core-next/issues/207 + } + + // TODO: Is it safe to do Promise.all here? + return Promise.all(resultPromises) + } } diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 7734e0219..0778bee7f 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -226,6 +226,21 @@ export class MapeoProject { return /** @type {EditableProjectSettings} */ ({}) } } + + /** + * @returns {Promise} + */ + async $getProjectInfo() { + const { createdAt, updatedAt } = await this.#dataTypes.project.getByDocId( + this.#projectId + ) + + return { + projectId: this.#projectId, + createdAt: new Date(createdAt), + updatedAt: new Date(updatedAt), + } + } } /** diff --git a/src/types.ts b/src/types.ts index 57343926d..bc27a8295 100644 --- a/src/types.ts +++ b/src/types.ts @@ -181,3 +181,9 @@ export type ProjectKeys = { 'auth' > } + +export type ProjectInfo = { + createdAt: Date + updatedAt: Date + projectId: string +} diff --git a/test-e2e/manager-basic.js b/test-e2e/manager-basic.js index c6f5e1e30..a438d3b7d 100644 --- a/test-e2e/manager-basic.js +++ b/test-e2e/manager-basic.js @@ -1,24 +1,28 @@ import { test } from 'brittle' import { MapeoManager } from '../src/mapeo-manager.js' -test('MapeoManager.createProject works', async (t) => { +test('Managing multiple projects', async (t) => { const manager = new MapeoManager() - const expectedSettings = { - name: 'foo', - } + const initialProjects = await manager.listProjects() - const projectId = await manager.createProject(expectedSettings) - - t.ok(projectId) + t.is( + initialProjects.length, + 0, + 'no projects exist when manager is initially created' + ) - const project = await manager.getProject(projectId) + const createdProjectIds = [ + await manager.createProject(), + await manager.createProject(), + await manager.createProject(), + ] - const settings = await project.$getProjectSettings() + const allProjects = await manager.listProjects() - t.is( - settings.name, - expectedSettings.name, - 'settings for fetched project are the same as when created' + t.is(allProjects.length, createdProjectIds.length) + t.ok( + allProjects.every((p) => createdProjectIds.includes(p.projectId)), + 'all created projects are listed' ) })