From 562e12a8a6a26da65118fe7d049f808dc8347c1f Mon Sep 17 00:00:00 2001 From: sethvincent Date: Wed, 16 Aug 2023 10:29:26 -0700 Subject: [PATCH 01/13] feat: $blobs.getUrl and $blobs.create methods --- src/blob-store/index.js | 52 +++++++++++++++++++++++++++++++++++++++++ src/mapeo-project.js | 21 +++++++++++++++++ src/types.ts | 4 ++-- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/blob-store/index.js b/src/blob-store/index.js index 7b71f4c0..98a65fe8 100644 --- a/src/blob-store/index.js +++ b/src/blob-store/index.js @@ -1,3 +1,4 @@ +import fs from 'fs' import Hyperdrive from 'hyperdrive' import b4a from 'b4a' import util from 'node:util' @@ -6,6 +7,8 @@ import { LiveDownload } from './live-download.js' /** @typedef {TypedEmitter<{ 'add-drive': (drive: import('hyperdrive')) => void }>} InternalDriveEmitter */ /** @typedef {import('../types.js').BlobId} BlobId */ +/** @typedef {import('../types.js').BlobType} BlobType */ +/** @typedef {import('../types.js').BlobVariant} BlobVariant */ // prop = blob type name // value = array of blob variants supported for that type @@ -274,6 +277,55 @@ class PretendCorestore { } } + /** + * Write blobs for provided variants of a file + * @param {{ original: string, preview?: string, thumbnail?: string }} filepaths + * @param {{ mimeType?: string }} metadata + * @returns {Promise<{ original: Omit, preview?: Omit, thumbnail?: Omit }>} + */ + async create(filepaths, metadata) { + const { original, preview, thumbnail } = filepaths + const { mimeType } = metadata + + const originalBlobId = await writeFile(original) + const previewBlobId = preview ? await writeFile(preview) : null + const thumbnailBlobId = thumbnail ? await writeFile(thumbnail) : null + + const blobIds = /** @type {{ original: Omit, preview?: Omit, thumbnail?: Omit }} */({ + original: originalBlobId, + }) + + if (previewBlobId) blobIds.preview = previewBlobId + if (thumbnailBlobId) blobIds.thumbnail = thumbnailBlobId + + return blobIds + + /** + * @param {string} filename + * @returns {Promise>} + */ + function writeFile (filename) { + const [type, variant, name] = this.splitPath(filename) + return new Promise((resolve, reject) => { + fs.createReadStream(filename) + .pipe(this.createWriteStream({ type, variant, name }, { metadata: { mimeType } })) + .on('error', reject) + .on('finish', () => { + resolve({ type, variant, name }) + }) + }) + } + } + + /** + * + * @param {string} filepath + * @returns {Omit} + */ + splitPath (filepath) { + const [type, variant, name] = /** @type {[BlobType, BlobVariant, String]} */(filepath.split('/')) + return { type, variant, name } + } /** no-op */ close() {} } diff --git a/src/mapeo-project.js b/src/mapeo-project.js index ebd87e77..6567b2ee 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -5,6 +5,7 @@ import { migrate } from 'drizzle-orm/better-sqlite3/migrator' import { CoreManager } from './core-manager/index.js' import { DataStore } from './datastore/index.js' import { DataType } from './datatype/index.js' +import { BlobStore } from './blobstore/index.js' import { IndexWriter } from './index-writer/index.js' import { fieldTable, observationTable, presetTable } from './schema/project.js' import RandomAccessFile from 'random-access-file' @@ -26,6 +27,7 @@ export class MapeoProject { #coreManager #dataStores #dataTypes + #blobStore /** * @param {Object} opts @@ -113,6 +115,25 @@ export class MapeoProject { db, }), } + + this.#blobStore = new BlobStore({ + coreManager: this.#coreManager, + }) + + this.$blobs = { + /** + * + * @param {import('./types.js').BlobId} blobId + * @returns {String} + */ + getUrl: (blobId) => { + const { driveId, type, variant, name } = blobId + // TODO: where is the hostname set? + // TODO: expose the projectId + return `${this.hostname}/${this.projectId}/${driveId}/${type}/${variant}/${name}` + }, + create: this.#blobStore.create.bind(this.#blobStore), + } } get observation() { diff --git a/src/types.ts b/src/types.ts index d723a46e..4aeefd96 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,8 +16,8 @@ import Corestore from 'corestore' import Hypercore from 'hypercore' type SupportedBlobVariants = typeof SUPPORTED_BLOB_VARIANTS -type BlobType = keyof SupportedBlobVariants -type BlobVariant = TupleToUnion< +export type BlobType = keyof SupportedBlobVariants +export type BlobVariant = TupleToUnion< SupportedBlobVariants[TBlobType] > From 1a51ed3e5ac948862a1c9429cd00ee8f62661cfc Mon Sep 17 00:00:00 2001 From: sethvincent Date: Thu, 17 Aug 2023 16:48:41 -0700 Subject: [PATCH 02/13] create BlobApi class for getUrl/create methods --- src/blob-api.js | 95 +++++++++++++++++++++++++++++++++++++++++ src/blob-store/index.js | 52 ---------------------- src/mapeo-project.js | 19 +++------ 3 files changed, 100 insertions(+), 66 deletions(-) create mode 100644 src/blob-api.js diff --git a/src/blob-api.js b/src/blob-api.js new file mode 100644 index 00000000..2c292c51 --- /dev/null +++ b/src/blob-api.js @@ -0,0 +1,95 @@ +import fs from 'fs' + +/** @typedef {import('./types.js').BlobId} BlobId */ +/** @typedef {import('./types.js').BlobType} BlobType */ +/** @typedef {import('./types.js').BlobVariant} BlobVariant */ + +export class BlobApi { + /** + * @param {object} options + * @param {string} options.projectId + */ + constructor({ projectId }) { + this.projectId = projectId + } + + /** + * Get a url for a blob based on its BlobId + * @param {import('./types.js').BlobId} blobId + * @returns {String} + */ + getUrl(blobId) { + const { driveId, type, variant, name } = blobId + return `http:///127.0.0.1/${this.projectId}/${driveId}/${type}/${variant}/${name}` + } + + /** + * Write blobs for provided variants of a file + * @param {{ original: string, preview?: string, thumbnail?: string }} filepaths + * @param {{ mimeType: string }} metadata + * @returns {Promise<{ original: Omit, preview?: Omit, thumbnail?: Omit }>} + */ + async create(filepaths, metadata) { + const { original, preview, thumbnail } = filepaths + const { mimeType } = metadata + const blobType = getType(mimeType) + + const originalBlobId = await writeFile({ + name: original, + variant: 'original', + type: blobType, + }) + const previewBlobId = preview + ? await writeFile({ name: preview, variant: 'preview', type: blobType }) + : null + const thumbnailBlobId = thumbnail + ? await writeFile({ + name: thumbnail, + variant: 'thumbnail', + type: blobType, + }) + : null + + const blobIds = + /** @type {{ original: Omit, preview?: Omit, thumbnail?: Omit }} */ ({ + original: originalBlobId, + }) + + if (previewBlobId) blobIds.preview = previewBlobId + if (thumbnailBlobId) blobIds.thumbnail = thumbnailBlobId + + return blobIds + + /** + * @param {Omit} options + * @returns {Promise>} + */ + function writeFile({ name, variant, type }) { + return new Promise((resolve, reject) => { + fs.createReadStream(name) + .pipe( + this.createWriteStream( + { type, variant, name }, + { metadata: { mimeType } } + ) + ) + .on('error', reject) + .on('finish', () => { + resolve({ type, variant, name }) + }) + }) + } + } +} + +/** + * @param {string} mimeType + * @returns {BlobType} + */ +function getType(mimeType) { + if (mimeType.startsWith('image')) return 'photo' + if (mimeType.startsWith('video')) return 'video' + if (mimeType.startsWith('audio')) return 'audio' + + throw new Error(`Unsupported mimeType: ${mimeType}`) +} diff --git a/src/blob-store/index.js b/src/blob-store/index.js index 98a65fe8..7b71f4c0 100644 --- a/src/blob-store/index.js +++ b/src/blob-store/index.js @@ -1,4 +1,3 @@ -import fs from 'fs' import Hyperdrive from 'hyperdrive' import b4a from 'b4a' import util from 'node:util' @@ -7,8 +6,6 @@ import { LiveDownload } from './live-download.js' /** @typedef {TypedEmitter<{ 'add-drive': (drive: import('hyperdrive')) => void }>} InternalDriveEmitter */ /** @typedef {import('../types.js').BlobId} BlobId */ -/** @typedef {import('../types.js').BlobType} BlobType */ -/** @typedef {import('../types.js').BlobVariant} BlobVariant */ // prop = blob type name // value = array of blob variants supported for that type @@ -277,55 +274,6 @@ class PretendCorestore { } } - /** - * Write blobs for provided variants of a file - * @param {{ original: string, preview?: string, thumbnail?: string }} filepaths - * @param {{ mimeType?: string }} metadata - * @returns {Promise<{ original: Omit, preview?: Omit, thumbnail?: Omit }>} - */ - async create(filepaths, metadata) { - const { original, preview, thumbnail } = filepaths - const { mimeType } = metadata - - const originalBlobId = await writeFile(original) - const previewBlobId = preview ? await writeFile(preview) : null - const thumbnailBlobId = thumbnail ? await writeFile(thumbnail) : null - - const blobIds = /** @type {{ original: Omit, preview?: Omit, thumbnail?: Omit }} */({ - original: originalBlobId, - }) - - if (previewBlobId) blobIds.preview = previewBlobId - if (thumbnailBlobId) blobIds.thumbnail = thumbnailBlobId - - return blobIds - - /** - * @param {string} filename - * @returns {Promise>} - */ - function writeFile (filename) { - const [type, variant, name] = this.splitPath(filename) - return new Promise((resolve, reject) => { - fs.createReadStream(filename) - .pipe(this.createWriteStream({ type, variant, name }, { metadata: { mimeType } })) - .on('error', reject) - .on('finish', () => { - resolve({ type, variant, name }) - }) - }) - } - } - - /** - * - * @param {string} filepath - * @returns {Omit} - */ - splitPath (filepath) { - const [type, variant, name] = /** @type {[BlobType, BlobVariant, String]} */(filepath.split('/')) - return { type, variant, name } - } /** no-op */ close() {} } diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 6567b2ee..7c1733a9 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -6,6 +6,7 @@ import { CoreManager } from './core-manager/index.js' import { DataStore } from './datastore/index.js' import { DataType } from './datatype/index.js' import { BlobStore } from './blobstore/index.js' +import { BlobApi } from './blob-api.js' import { IndexWriter } from './index-writer/index.js' import { fieldTable, observationTable, presetTable } from './schema/project.js' import RandomAccessFile from 'random-access-file' @@ -24,6 +25,7 @@ const INDEXER_STORAGE_FOLDER_NAME = 'indexer' const MAX_FILE_DESCRIPTORS = 768 export class MapeoProject { + #projectId #coreManager #dataStores #dataTypes @@ -38,6 +40,8 @@ export class MapeoProject { * @param {Partial>} [opts.encryptionKeys] Encryption keys for each namespace */ constructor({ storagePath, ...coreManagerOpts }) { + this.#projectId = coreManagerOpts.projectKey.toString('hex') // TODO: update based on outcome of https://github.com/digidem/mapeo-core-next/issues/171 + ///////// 1. Setup database const dbPath = @@ -120,20 +124,7 @@ export class MapeoProject { coreManager: this.#coreManager, }) - this.$blobs = { - /** - * - * @param {import('./types.js').BlobId} blobId - * @returns {String} - */ - getUrl: (blobId) => { - const { driveId, type, variant, name } = blobId - // TODO: where is the hostname set? - // TODO: expose the projectId - return `${this.hostname}/${this.projectId}/${driveId}/${type}/${variant}/${name}` - }, - create: this.#blobStore.create.bind(this.#blobStore), - } + this.$blobs = new BlobApi({ projectId: this.#projectId }) } get observation() { From 6bd54d0a3601ae8400501adfd63e421e5d630d3f Mon Sep 17 00:00:00 2001 From: sethvincent Date: Thu, 17 Aug 2023 17:01:03 -0700 Subject: [PATCH 03/13] use hash for blob names in create method --- src/blob-api.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/blob-api.js b/src/blob-api.js index 2c292c51..e2b0886b 100644 --- a/src/blob-api.js +++ b/src/blob-api.js @@ -1,4 +1,6 @@ import fs from 'fs' +import sodium from 'sodium-universal' +import b4a from 'b4a' /** @typedef {import('./types.js').BlobId} BlobId */ /** @typedef {import('./types.js').BlobType} BlobType */ @@ -33,18 +35,20 @@ export class BlobApi { const { original, preview, thumbnail } = filepaths const { mimeType } = metadata const blobType = getType(mimeType) + const hash = b4a.alloc(sodium.crypto_generichash_BYTES) + sodium.crypto_generichash(hash, b4a.from(original)) const originalBlobId = await writeFile({ - name: original, + name: hash, variant: 'original', type: blobType, }) const previewBlobId = preview - ? await writeFile({ name: preview, variant: 'preview', type: blobType }) + ? await writeFile({ name: hash, variant: 'preview', type: blobType }) : null const thumbnailBlobId = thumbnail ? await writeFile({ - name: thumbnail, + name: hash, variant: 'thumbnail', type: blobType, }) From 714a6055fba477f40b12c73cfbec9591b7c4a0d4 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Tue, 22 Aug 2023 14:41:35 -0700 Subject: [PATCH 04/13] add getPort function, use random values as part of ingested filenames, pass BlobStore to BlobApi, add tests --- src/blob-api.js | 97 +++++++++++-------- src/blob-server/index.js | 16 ++++ src/mapeo-project.js | 5 +- tests/blob-api.js | 128 ++++++++++++++++++++++++++ tests/blob-server.js | 10 +- tests/fixtures/blob-api/original.png | Bin 0 -> 769 bytes tests/fixtures/blob-api/preview.png | Bin 0 -> 768 bytes tests/fixtures/blob-api/thumbnail.png | Bin 0 -> 774 bytes tests/helpers/blob-server.js | 0 tests/helpers/blob-store.js | 14 +++ tests/helpers/index.js | 10 ++ 11 files changed, 234 insertions(+), 46 deletions(-) create mode 100644 tests/blob-api.js create mode 100644 tests/fixtures/blob-api/original.png create mode 100644 tests/fixtures/blob-api/preview.png create mode 100644 tests/fixtures/blob-api/thumbnail.png create mode 100644 tests/helpers/blob-server.js diff --git a/src/blob-api.js b/src/blob-api.js index e2b0886b..58eb8813 100644 --- a/src/blob-api.js +++ b/src/blob-api.js @@ -1,7 +1,10 @@ -import fs from 'fs' +import fs from 'node:fs' +import { basename } from 'node:path' import sodium from 'sodium-universal' import b4a from 'b4a' +import { getPort } from './blob-server/index.js' + /** @typedef {import('./types.js').BlobId} BlobId */ /** @typedef {import('./types.js').BlobType} BlobType */ /** @typedef {import('./types.js').BlobVariant} BlobVariant */ @@ -10,19 +13,24 @@ export class BlobApi { /** * @param {object} options * @param {string} options.projectId + * @param {import('./blob-store/index.js').BlobStore} options.blobStore + * @param {import('fastify').FastifyInstance} options.blobServer */ - constructor({ projectId }) { + constructor({ projectId, blobStore, blobServer }) { this.projectId = projectId + this.blobStore = blobStore + this.blobServer = blobServer } /** * Get a url for a blob based on its BlobId * @param {import('./types.js').BlobId} blobId - * @returns {String} + * @returns {Promise} */ - getUrl(blobId) { + async getUrl(blobId) { const { driveId, type, variant, name } = blobId - return `http:///127.0.0.1/${this.projectId}/${driveId}/${type}/${variant}/${name}` + const port = await getPort(this.blobServer.server) + return `http://127.0.0.1:${port}/${this.projectId}/${driveId}/${type}/${variant}/${name}` } /** @@ -35,23 +43,40 @@ export class BlobApi { const { original, preview, thumbnail } = filepaths const { mimeType } = metadata const blobType = getType(mimeType) - const hash = b4a.alloc(sodium.crypto_generichash_BYTES) - sodium.crypto_generichash(hash, b4a.from(original)) + const hash = b4a.alloc(8) + sodium.randombytes_buf(hash) + const name = hash.toString('hex') - const originalBlobId = await writeFile({ - name: hash, - variant: 'original', - type: blobType, - }) + const originalBlobId = await this.writeFile( + original, + { + name: `${name}_${basename(original)}`, + variant: 'original', + type: blobType, + }, + metadata + ) const previewBlobId = preview - ? await writeFile({ name: hash, variant: 'preview', type: blobType }) + ? await this.writeFile( + preview, + { + name: `${name}_${basename(preview)}`, + variant: 'preview', + type: blobType, + }, + metadata + ) : null const thumbnailBlobId = thumbnail - ? await writeFile({ - name: hash, - variant: 'thumbnail', - type: blobType, - }) + ? await this.writeFile( + thumbnail, + { + name: `${name}_${basename(thumbnail)}`, + variant: 'thumbnail', + type: blobType, + }, + metadata + ) : null const blobIds = @@ -63,26 +88,26 @@ export class BlobApi { if (thumbnailBlobId) blobIds.thumbnail = thumbnailBlobId return blobIds + } - /** - * @param {Omit} options - * @returns {Promise>} - */ - function writeFile({ name, variant, type }) { - return new Promise((resolve, reject) => { - fs.createReadStream(name) - .pipe( - this.createWriteStream( - { type, variant, name }, - { metadata: { mimeType } } - ) + /** + * @param {Omit} options + * @returns {Promise>} + */ + async writeFile(filepath, { name, variant, type }, metadata) { + return new Promise((resolve, reject) => { + fs.createReadStream(filepath) + .pipe( + this.blobStore.createWriteStream( + { type, variant, name }, + { metadata } ) - .on('error', reject) - .on('finish', () => { - resolve({ type, variant, name }) - }) - }) - } + ) + .on('error', reject) + .on('finish', () => { + resolve({ type, variant, name }) + }) + }) } } diff --git a/src/blob-server/index.js b/src/blob-server/index.js index e64c8493..8b1a9906 100644 --- a/src/blob-server/index.js +++ b/src/blob-server/index.js @@ -1,3 +1,4 @@ +import { once } from 'events' import fastify from 'fastify' import BlobServerPlugin from './fastify-plugin.js' @@ -23,3 +24,18 @@ export function createBlobServer({ logger, blobStore, prefix, projectId }) { }) return server } + +/** + * @param {import('node:http').Server} server + * @returns {Promise} + */ +export async function getPort(server) { + const address = server.address() + + if (!address || !(typeof address === 'object') || !address.port) { + await once(server, 'listening') + return getPort(server) + } + + return address.port +} diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 7c1733a9..db4ba687 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -124,7 +124,10 @@ export class MapeoProject { coreManager: this.#coreManager, }) - this.$blobs = new BlobApi({ projectId: this.#projectId }) + this.$blobs = new BlobApi({ + projectId: this.#projectId, + blobStore: this.#blobStore, + }) } get observation() { diff --git a/tests/blob-api.js b/tests/blob-api.js new file mode 100644 index 00000000..858c1435 --- /dev/null +++ b/tests/blob-api.js @@ -0,0 +1,128 @@ +import { join, basename } from 'node:path' +import { fileURLToPath } from 'url' +import test from 'brittle' +import { BlobApi } from '../src/blob-api.js' +import { createBlobServer, getPort } from '../src/blob-server/index.js' +import { createBlobStore } from './helpers/blob-store.js' +import { timeoutException } from './helpers/index.js' + +test('get port after listening event with explicit port', async (t) => { + const blobStore = createBlobStore() + const server = await createBlobServer({ blobStore }) + + t.ok(await timeoutException(getPort(server.server))) + + await new Promise((resolve) => { + server.listen({ port: 3456 }, (err, address) => { + resolve(address) + }) + }) + + const port = await getPort(server.server) + + t.is(typeof port, 'number') + t.is(port, 3456) + + t.teardown(async () => { + await server.close() + }) +}) + +test('get port after listening event with unset port', async (t) => { + const blobStore = createBlobStore() + const server = await createBlobServer({ blobStore }) + + t.ok(await timeoutException(getPort(server.server))) + + await new Promise((resolve) => { + server.listen({ port: 0 }, (err, address) => { + resolve(address) + }) + }) + + const port = await getPort(server.server) + + t.is(typeof port, 'number', 'port is a number') + t.teardown(async () => { + await server.close() + }) +}) + +test('get url from blobId', async (t) => { + const projectId = '1234' + const driveId = '1234' + const type = 'image' + const variant = 'original' + const name = '1234' + + const blobStore = createBlobStore() + const blobServer = await createBlobServer({ blobStore }) + const blobApi = new BlobApi({ projectId: '1234', blobStore, blobServer }) + + await new Promise((resolve) => { + blobServer.listen({ port: 0 }, (err, address) => { + resolve(address) + }) + }) + + const url = await blobApi.getUrl({ driveId, type, variant, name }) + + t.is( + url, + `http://127.0.0.1:${ + blobServer.server.address().port + }/${projectId}/${driveId}/${type}/${variant}/${name}` + ) + t.teardown(async () => { + await blobServer.close() + }) +}) + +test('create blobs', async (t) => { + const projectId = '1234' + const driveId = '1234' + const type = 'image' + const variant = 'original' + const name = '1234' + + const { blobStore } = createBlobStore() + const blobServer = await createBlobServer({ blobStore }) + const blobApi = new BlobApi({ projectId: '1234', blobStore, blobServer }) + + await new Promise((resolve) => { + blobServer.listen({ port: 0 }, (err, address) => { + resolve(address) + }) + }) + + const directory = fileURLToPath( + new URL('./fixtures/blob-api/', import.meta.url) + ) + + const blobIds = await blobApi.create( + { + original: join(directory, 'original.png'), + preview: join(directory, 'preview.png'), + thumbnail: join(directory, 'thumbnail.png'), + }, + { + mimeType: 'image/png', + } + ) + + t.is(blobIds.original.type, 'photo') + t.is(blobIds.original.variant, 'original') + t.ok(blobIds.original.name.includes('_original.png')) + + t.is(blobIds.preview.type, 'photo') + t.is(blobIds.preview.variant, 'preview') + t.ok(blobIds.preview.name.includes('_preview.png')) + + t.is(blobIds.thumbnail.type, 'photo') + t.is(blobIds.thumbnail.variant, 'thumbnail') + t.ok(blobIds.thumbnail.name.includes('_thumbnail.png')) + + t.teardown(async () => { + await blobServer.close() + }) +}) diff --git a/tests/blob-server.js b/tests/blob-server.js index 1a1cb33f..1afe1f44 100644 --- a/tests/blob-server.js +++ b/tests/blob-server.js @@ -3,13 +3,11 @@ import test from 'brittle' import { readdirSync } from 'fs' import { readFile } from 'fs/promises' import path from 'path' -import { createCoreManager } from './helpers/core-manager.js' -import { BlobStore } from '../src/blob-store/index.js' import { createBlobServer } from '../src/blob-server/index.js' import BlobServerPlugin from '../src/blob-server/fastify-plugin.js' import fastify from 'fastify' -import { replicateBlobs } from './helpers/blob-store.js' +import { replicateBlobs, createBlobStore } from './helpers/blob-store.js' test('Plugin throws error if missing getBlobStore option', async (t) => { const server = fastify() @@ -217,12 +215,6 @@ test('GET photo returns 404 when trying to get non-replicated blob', async (t) = t.is(res.statusCode, 404) }) -function createBlobStore(opts) { - const coreManager = createCoreManager(opts) - const blobStore = new BlobStore({ coreManager }) - return { blobStore, coreManager } -} - async function testenv({ prefix, logger } = {}) { const projectKey = randomBytes(32) const projectId = projectKey.toString('hex') diff --git a/tests/fixtures/blob-api/original.png b/tests/fixtures/blob-api/original.png new file mode 100644 index 0000000000000000000000000000000000000000..1b5b2257f7e754c746b4a51c82c42e29d5176f02 GIT binary patch literal 769 zcmeAS@N?(olHy`uVBq!ia0vp^YeATU4M-kZ|KAx%aTa()7Bet#3xhBt!>l*8o|0J?9FfdK?ba4!+V0?SSJ1{v=!tJ7SI5z|P0|w3o z409S-RTeNjQV5)2;US|U^r+!k+O<#~{^r=N*HrpHu3P`Fdi}nAyc|D`72LKw>6j*S zaguUWk&Aa<#z{e4%SEd0LqqT{Ykq$I}dfwI-^S1A1*`@QQYVW=|9C9a~ZQdfRd-~Uh4+eSp`TF~(Z?%=-OWu8# z@7Yi58A+YRAA#Wh|I@QuQX8kwn0dI;;A9coACYITU$5TIG1-#;#Kxf!o>YnVIrY&| WfsdON4}JosSO!m5KbLh*2~7ZX%1z7w literal 0 HcmV?d00001 diff --git a/tests/fixtures/blob-api/preview.png b/tests/fixtures/blob-api/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..56d4625a1aa31da6da8556f79fe12621b80a17e8 GIT binary patch literal 768 zcmeAS@N?(olHy`uVBq!ia0vp^YeATU4M-kZ|KAx%aTa()7Bet#3xhBt!>l*8o|0J?9FfdK^ba4!+V0?Qc&^I|y#Np!U6cz(;=v9xk%N0 z%ae|2G8ZQ)M-{nv_hp$}zi3yqpmd`ez@VyrsW;`nbY_#&bxT} z^l78g|NAvQR4=k!{I+a&jbB{x%}jOcSnhqXv9Y?>TQ1M9oU`Y|M!5+?Dp)Cs{(p>z XH9fj}Z*R~6CRhefS3j3^P6l*8o|0J?9Ffh&bba4!+V0?SyqOY`ph(lm-DZ`Bh!33rw zEE|}V4H%P})=c7^B)G}Z$jNMhMf}=dk;^YlJ@<3-{`K?b)qeh(>u{o<(QuKf`<5pi z(_}7AQjRKe@$SnwDX42XGz8x!&7VL2@z0-?Hg+qZ8ow3^G7{Z~fk^S^&~eC>yS z{P-aO6iwcLKc4B{+p@?1{?%_r9RoMUl literal 0 HcmV?d00001 diff --git a/tests/helpers/blob-server.js b/tests/helpers/blob-server.js new file mode 100644 index 00000000..e69de29b diff --git a/tests/helpers/blob-store.js b/tests/helpers/blob-store.js index c9fb57d3..3c17b42e 100644 --- a/tests/helpers/blob-store.js +++ b/tests/helpers/blob-store.js @@ -1,6 +1,20 @@ import { replicate } from './core-manager.js' import { pipelinePromise as pipeline, Writable } from 'streamx' +import { BlobStore } from '../../src/blob-store/index.js' +import { createCoreManager } from './core-manager.js' + +/** + * + * @param {Object} options + * @returns + */ +export function createBlobStore(options = {}) { + const coreManager = createCoreManager(options) + const blobStore = new BlobStore({ coreManager }) + return { blobStore, coreManager } +} + /** * * @param {import('../../src/core-manager/index.js').CoreManager} cm1 diff --git a/tests/helpers/index.js b/tests/helpers/index.js index a26980c1..6629c19a 100644 --- a/tests/helpers/index.js +++ b/tests/helpers/index.js @@ -73,3 +73,13 @@ export async function waitForIndexing(stores) { }) ) } + +export async function timeoutException(promise, timeout = 100) { + const timer = new Promise((resolve) => { + setTimeout(() => { + resolve('timeout') + }, timeout) + }) + + return (await Promise.race([promise, timer])) === 'timeout' +} From dae3c337ac3b5702cebaa3bc78cf1893d1c59912 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Tue, 22 Aug 2023 15:41:06 -0700 Subject: [PATCH 05/13] remove unused code --- tests/blob-api.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/blob-api.js b/tests/blob-api.js index 858c1435..8b6d0732 100644 --- a/tests/blob-api.js +++ b/tests/blob-api.js @@ -79,12 +79,6 @@ test('get url from blobId', async (t) => { }) test('create blobs', async (t) => { - const projectId = '1234' - const driveId = '1234' - const type = 'image' - const variant = 'original' - const name = '1234' - const { blobStore } = createBlobStore() const blobServer = await createBlobServer({ blobStore }) const blobApi = new BlobApi({ projectId: '1234', blobStore, blobServer }) From 96dc00e3a3467d6de8fc3197e002a313ee4b7d84 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Wed, 23 Aug 2023 16:29:48 -0700 Subject: [PATCH 06/13] update blob-api with type/param fixes --- src/blob-api.js | 90 +++++++++++++++++++--------------------- src/blob-server/index.js | 1 - src/mapeo-project.js | 13 +++++- tests/blob-api.js | 18 +++----- 4 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/blob-api.js b/src/blob-api.js index 58eb8813..a84fb078 100644 --- a/src/blob-api.js +++ b/src/blob-api.js @@ -1,5 +1,5 @@ import fs from 'node:fs' -import { basename } from 'node:path' +import { pipeline } from 'node:stream/promises' import sodium from 'sodium-universal' import b4a from 'b4a' @@ -36,8 +36,8 @@ export class BlobApi { /** * Write blobs for provided variants of a file * @param {{ original: string, preview?: string, thumbnail?: string }} filepaths - * @param {{ mimeType: string }} metadata - * @returns {Promise<{ original: Omit, preview?: Omit, thumbnail?: Omit }>} + * @param {{ mimeType: string, driveId: string }} metadata + * @returns {Promise<{ driveId: string, name: string, type: 'photo' | 'video' | 'audio' }>} */ async create(filepaths, metadata) { const { original, preview, thumbnail } = filepaths @@ -47,67 +47,61 @@ export class BlobApi { sodium.randombytes_buf(hash) const name = hash.toString('hex') - const originalBlobId = await this.writeFile( + await this.writeFile( original, { - name: `${name}_${basename(original)}`, + name: `${name}`, variant: 'original', type: blobType, }, metadata ) - const previewBlobId = preview - ? await this.writeFile( - preview, - { - name: `${name}_${basename(preview)}`, - variant: 'preview', - type: blobType, - }, - metadata - ) - : null - const thumbnailBlobId = thumbnail - ? await this.writeFile( - thumbnail, - { - name: `${name}_${basename(thumbnail)}`, - variant: 'thumbnail', - type: blobType, - }, - metadata - ) - : null - const blobIds = - /** @type {{ original: Omit, preview?: Omit, thumbnail?: Omit }} */ ({ - original: originalBlobId, - }) + if (preview) { + await this.writeFile( + preview, + { + name: `${name}`, + variant: 'preview', + type: blobType, + }, + metadata + ) + } - if (previewBlobId) blobIds.preview = previewBlobId - if (thumbnailBlobId) blobIds.thumbnail = thumbnailBlobId + if (thumbnail) { + await this.writeFile( + thumbnail, + { + name: `${name}`, + variant: 'thumbnail', + type: blobType, + }, + metadata + ) + } - return blobIds + return { + driveId: metadata.driveId, + name, + type: blobType, + } } /** + * @param {string} filepath * @param {Omit} options - * @returns {Promise>} + * @param {object} metadata + * @param {string} metadata.mimeType */ async writeFile(filepath, { name, variant, type }, metadata) { - return new Promise((resolve, reject) => { - fs.createReadStream(filepath) - .pipe( - this.blobStore.createWriteStream( - { type, variant, name }, - { metadata } - ) - ) - .on('error', reject) - .on('finish', () => { - resolve({ type, variant, name }) - }) - }) + // @ts-ignore TODO: address blobStore.createWriteStream return type + await pipeline( + fs.createReadStream(filepath), + this.blobStore.createWriteStream({ type, variant, name }, { metadata }) + ) + + return { name, variant, type } } } diff --git a/src/blob-server/index.js b/src/blob-server/index.js index 8b1a9906..89e65eb1 100644 --- a/src/blob-server/index.js +++ b/src/blob-server/index.js @@ -7,7 +7,6 @@ import BlobServerPlugin from './fastify-plugin.js' * @param {object} opts * @param {import('fastify').FastifyServerOptions['logger']} opts.logger * @param {import('../blob-store/index.js').BlobStore} opts.blobStore - * @param {import('./fastify-plugin.js').BlobServerPluginOpts['getBlobStore']} opts.getBlobStore * @param {import('fastify').RegisterOptions['prefix']} opts.prefix * @param {string} opts.projectId Temporary option to enable `getBlobStore` option. Will be removed when multiproject support in Mapeo class is implemented. * diff --git a/src/mapeo-project.js b/src/mapeo-project.js index db4ba687..b305e2a0 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -5,8 +5,9 @@ import { migrate } from 'drizzle-orm/better-sqlite3/migrator' import { CoreManager } from './core-manager/index.js' import { DataStore } from './datastore/index.js' import { DataType } from './datatype/index.js' -import { BlobStore } from './blobstore/index.js' +import { BlobStore } from './blob-store/index.js' import { BlobApi } from './blob-api.js' +import { createBlobServer } from './blob-server/index.js' import { IndexWriter } from './index-writer/index.js' import { fieldTable, observationTable, presetTable } from './schema/project.js' import RandomAccessFile from 'random-access-file' @@ -30,6 +31,7 @@ export class MapeoProject { #dataStores #dataTypes #blobStore + #blobServer /** * @param {Object} opts @@ -124,9 +126,18 @@ export class MapeoProject { coreManager: this.#coreManager, }) + this.#blobServer = createBlobServer({ + logger: true, + blobStore: this.#blobStore, + prefix: `/blobs/`, + projectId: this.#projectId, + }) + + // @ts-ignore TODO: pass in blobServer this.$blobs = new BlobApi({ projectId: this.#projectId, blobStore: this.#blobStore, + blobServer: this.#blobServer, }) } diff --git a/tests/blob-api.js b/tests/blob-api.js index 8b6d0732..0858940b 100644 --- a/tests/blob-api.js +++ b/tests/blob-api.js @@ -1,4 +1,4 @@ -import { join, basename } from 'node:path' +import { join } from 'node:path' import { fileURLToPath } from 'url' import test from 'brittle' import { BlobApi } from '../src/blob-api.js' @@ -93,28 +93,20 @@ test('create blobs', async (t) => { new URL('./fixtures/blob-api/', import.meta.url) ) - const blobIds = await blobApi.create( + const attachment = await blobApi.create( { original: join(directory, 'original.png'), preview: join(directory, 'preview.png'), thumbnail: join(directory, 'thumbnail.png'), }, { + driveId: '1234', mimeType: 'image/png', } ) - t.is(blobIds.original.type, 'photo') - t.is(blobIds.original.variant, 'original') - t.ok(blobIds.original.name.includes('_original.png')) - - t.is(blobIds.preview.type, 'photo') - t.is(blobIds.preview.variant, 'preview') - t.ok(blobIds.preview.name.includes('_preview.png')) - - t.is(blobIds.thumbnail.type, 'photo') - t.is(blobIds.thumbnail.variant, 'thumbnail') - t.ok(blobIds.thumbnail.name.includes('_thumbnail.png')) + t.is(attachment.driveId, '1234') + t.is(attachment.type, 'photo') t.teardown(async () => { await blobServer.close() From 1a11b68551c24956d529fc44ffb2f1bd5e864139 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Wed, 23 Aug 2023 16:32:40 -0700 Subject: [PATCH 07/13] change how blobServer is added to mapeo-project --- src/mapeo-project.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/mapeo-project.js b/src/mapeo-project.js index b305e2a0..db2dea87 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -36,12 +36,13 @@ export class MapeoProject { /** * @param {Object} opts * @param {string} [opts.storagePath] Folder for all data storage (hypercores and sqlite db). Folder must exist. If not defined, everything is stored in-memory + * @param {import('fastify').FastifyInstance} opts.blobServer * @param {import('@mapeo/crypto').KeyManager} opts.keyManager mapeo/crypto KeyManager instance * @param {Buffer} opts.projectKey 32-byte public key of the project creator core * @param {Buffer} [opts.projectSecretKey] 32-byte secret key of the project creator core * @param {Partial>} [opts.encryptionKeys] Encryption keys for each namespace */ - constructor({ storagePath, ...coreManagerOpts }) { + constructor({ storagePath, blobServer, ...coreManagerOpts }) { this.#projectId = coreManagerOpts.projectKey.toString('hex') // TODO: update based on outcome of https://github.com/digidem/mapeo-core-next/issues/171 ///////// 1. Setup database @@ -126,12 +127,7 @@ export class MapeoProject { coreManager: this.#coreManager, }) - this.#blobServer = createBlobServer({ - logger: true, - blobStore: this.#blobStore, - prefix: `/blobs/`, - projectId: this.#projectId, - }) + this.#blobServer = blobServer // @ts-ignore TODO: pass in blobServer this.$blobs = new BlobApi({ From 56cba57dc66e13654db38281ce9541a9c35c1d03 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Wed, 23 Aug 2023 16:33:00 -0700 Subject: [PATCH 08/13] lintfix --- src/mapeo-project.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mapeo-project.js b/src/mapeo-project.js index db2dea87..4191ad75 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -7,7 +7,6 @@ import { DataStore } from './datastore/index.js' import { DataType } from './datatype/index.js' import { BlobStore } from './blob-store/index.js' import { BlobApi } from './blob-api.js' -import { createBlobServer } from './blob-server/index.js' import { IndexWriter } from './index-writer/index.js' import { fieldTable, observationTable, presetTable } from './schema/project.js' import RandomAccessFile from 'random-access-file' From f65dafd299363c2b534d3320321142d614a371a2 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Wed, 23 Aug 2023 16:58:17 -0700 Subject: [PATCH 09/13] type fixes --- test-e2e/project-crud.js | 11 +++++++++++ test-types/data-types.ts | 24 ++++++++++++++++++++++-- tests/blob-api.js | 2 +- tests/helpers/blob-store.js | 1 + tests/helpers/core-manager.js | 1 + 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/test-e2e/project-crud.js b/test-e2e/project-crud.js index 89c2fb1f..f698db81 100644 --- a/test-e2e/project-crud.js +++ b/test-e2e/project-crud.js @@ -2,6 +2,8 @@ import { test } from 'brittle' import { randomBytes } from 'crypto' import { KeyManager } from '@mapeo/crypto' import { MapeoProject } from '../src/mapeo-project.js' +import { createBlobServer } from '../src/blob-server/index.js' +import { createBlobStore } from '../tests/helpers/blob-store.js' /** @satisfies {Array} */ const fixtures = [ @@ -131,9 +133,18 @@ function createProject({ projectKey = randomBytes(32), } = {}) { const keyManager = new KeyManager(rootKey) + const { blobStore } = createBlobStore() + const blobServer = createBlobServer({ + blobStore, + logger: true, + prefix: '/', + projectId: projectKey.toString('hex'), + }) + return new MapeoProject({ keyManager, projectKey, + blobServer, }) } diff --git a/test-types/data-types.ts b/test-types/data-types.ts index 53f49214..5003bd7f 100644 --- a/test-types/data-types.ts +++ b/test-types/data-types.ts @@ -10,15 +10,35 @@ import { PresetValue, } from '@mapeo/schema' import { Expect, type Equal } from './utils.js' +import { createBlobServer } from '../dist/blob-server/index.js' +import { BlobStore } from '../dist/blob-store/index.js' +import { CoreManager } from '../dist/core-manager/index.js' +import Database from 'better-sqlite3' +import RandomAccessMemory from 'random-access-memory' type Forks = { forks: string[] } type ObservationWithForks = Observation & Forks type PresetWithForks = Preset & Forks type FieldWithForks = Field & Forks +const projectKey = randomBytes(32) +const keyManager = new KeyManager(randomBytes(32)) +const coreManager = new CoreManager({ + sqlite: new Database(':memory:'), + keyManager, + projectKey, + storage: (name) => new RandomAccessMemory(name), +}) + const mapeoProject = new MapeoProject({ - keyManager: new KeyManager(randomBytes(32)), - projectKey: randomBytes(32), + keyManager, + projectKey, + blobServer: createBlobServer({ + logger: true, + prefix: '/', + blobStore: new BlobStore({ coreManager }), + projectId: 'abc', + }), }) ///// Observations diff --git a/tests/blob-api.js b/tests/blob-api.js index 0858940b..d8cb9cf8 100644 --- a/tests/blob-api.js +++ b/tests/blob-api.js @@ -80,7 +80,7 @@ test('get url from blobId', async (t) => { test('create blobs', async (t) => { const { blobStore } = createBlobStore() - const blobServer = await createBlobServer({ blobStore }) + const blobServer = createBlobServer({ blobStore }) const blobApi = new BlobApi({ projectId: '1234', blobStore, blobServer }) await new Promise((resolve) => { diff --git a/tests/helpers/blob-store.js b/tests/helpers/blob-store.js index 3c17b42e..21b528e9 100644 --- a/tests/helpers/blob-store.js +++ b/tests/helpers/blob-store.js @@ -1,3 +1,4 @@ +// @ts-nocheck import { replicate } from './core-manager.js' import { pipelinePromise as pipeline, Writable } from 'streamx' diff --git a/tests/helpers/core-manager.js b/tests/helpers/core-manager.js index 377155ee..be439481 100644 --- a/tests/helpers/core-manager.js +++ b/tests/helpers/core-manager.js @@ -1,3 +1,4 @@ +// @ts-nocheck import { CoreManager } from '../../src/core-manager/index.js' import Sqlite from 'better-sqlite3' import { randomBytes } from 'crypto' From 849805a24d974483a2181634b9cbe4887ee15518 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Thu, 24 Aug 2023 17:16:11 -0700 Subject: [PATCH 10/13] fix merge conflicts, move blobserver and blobstore instantiation into MapeoProject class --- src/mapeo-project.js | 11 +++++++---- test-e2e/project-crud.js | 35 ----------------------------------- test-types/data-types.ts | 17 ----------------- 3 files changed, 7 insertions(+), 56 deletions(-) diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 50932ae4..e934a134 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -7,6 +7,7 @@ import { CoreManager } from './core-manager/index.js' import { DataStore } from './datastore/index.js' import { DataType, kCreateWithDocId } from './datatype/index.js' import { BlobStore } from './blob-store/index.js' +import { createBlobServer } from './blob-server/index.js' import { BlobApi } from './blob-api.js' import { IndexWriter } from './index-writer/index.js' import { projectTable } from './schema/client.js' @@ -36,12 +37,10 @@ export class MapeoProject { #dataTypes #blobStore #blobServer - #projectId /** * @param {Object} opts * @param {string} [opts.storagePath] Folder for all data storage (hypercores and sqlite db). Folder must exist. If not defined, everything is stored in-memory - * @param {import('fastify').FastifyInstance} opts.blobServer * @param {import('@mapeo/crypto').KeyManager} opts.keyManager mapeo/crypto KeyManager instance * @param {Buffer} opts.projectKey 32-byte public key of the project creator core * @param {Buffer} [opts.projectSecretKey] 32-byte secret key of the project creator core @@ -53,7 +52,6 @@ export class MapeoProject { storagePath, sharedDb, sharedIndexWriter, - blobServer, ...coreManagerOpts }) { // TODO: Update to use @mapeo/crypto when ready (https://github.com/digidem/mapeo-core-next/issues/171) @@ -150,7 +148,12 @@ export class MapeoProject { coreManager: this.#coreManager, }) - this.#blobServer = blobServer + this.#blobServer = createBlobServer({ + logger: true, + blobStore: this.#blobStore, + prefix: '/blobs/', + projectId: this.#projectId, + }) // @ts-ignore TODO: pass in blobServer this.$blobs = new BlobApi({ diff --git a/test-e2e/project-crud.js b/test-e2e/project-crud.js index fa8f6dd5..688e14bf 100644 --- a/test-e2e/project-crud.js +++ b/test-e2e/project-crud.js @@ -1,10 +1,6 @@ import { test } from 'brittle' import { randomBytes } from 'crypto' import { KeyManager } from '@mapeo/crypto' - -import { MapeoProject } from '../src/mapeo-project.js' -import { createBlobServer } from '../src/blob-server/index.js' -import { createBlobStore } from '../tests/helpers/blob-store.js' import { valueOf } from '../src/utils.js' import { MapeoManager } from '../src/mapeo-manager.js' @@ -124,37 +120,6 @@ test('CRUD operations', async (t) => { } }) -/** - * @template {import('@mapeo/schema').MapeoDoc & { forks: string[] }} T - * @param {T} doc - * @returns {Omit} - */ -function valueOf(doc) { - // eslint-disable-next-line no-unused-vars - const { docId, versionId, links, forks, createdAt, updatedAt, ...rest } = doc - return rest -} - -function createProject({ - rootKey = randomBytes(16), - projectKey = randomBytes(32), -} = {}) { - const keyManager = new KeyManager(rootKey) - const { blobStore } = createBlobStore() - const blobServer = createBlobServer({ - blobStore, - logger: true, - prefix: '/', - projectId: projectKey.toString('hex'), - }) - - return new MapeoProject({ - keyManager, - projectKey, - blobServer, - }) -} - /** * Remove undefined properties from an object, to allow deep comparison * @param {object} obj diff --git a/test-types/data-types.ts b/test-types/data-types.ts index da196528..2204d1f7 100644 --- a/test-types/data-types.ts +++ b/test-types/data-types.ts @@ -14,11 +14,6 @@ import { drizzle } from 'drizzle-orm/better-sqlite3' import { IndexWriter } from '../dist/index-writer/index.js' import { projectTable } from '../dist/schema/client.js' import { Expect, type Equal } from './utils.js' -import { createBlobServer } from '../dist/blob-server/index.js' -import { BlobStore } from '../dist/blob-store/index.js' -import { CoreManager } from '../dist/core-manager/index.js' -import Database from 'better-sqlite3' -import RandomAccessMemory from 'random-access-memory' type Forks = { forks: string[] } type ObservationWithForks = Observation & Forks @@ -29,21 +24,9 @@ const projectKey = randomBytes(32) const keyManager = new KeyManager(randomBytes(32)) const sqlite = new Database(':memory:') -const coreManager = new CoreManager({ - sqlite, - keyManager, - projectKey, - storage: (name) => new RandomAccessMemory(name), -}) - const mapeoProject = new MapeoProject({ keyManager, projectKey, - blobServer: createBlobServer({ - logger: true, - prefix: '/', - blobStore: new BlobStore({ coreManager }), - projectId: 'abc', sharedDb: drizzle(sqlite), sharedIndexWriter: new IndexWriter({ tables: [projectTable], From 46e601bf316aec94f542ab7c931ad75232415761 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Thu, 31 Aug 2023 08:33:23 -0700 Subject: [PATCH 11/13] use blobstore.writerdriveid in blob-api.js --- src/blob-api.js | 6 +++--- tests/blob-api.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blob-api.js b/src/blob-api.js index a84fb078..6e776deb 100644 --- a/src/blob-api.js +++ b/src/blob-api.js @@ -36,7 +36,7 @@ export class BlobApi { /** * Write blobs for provided variants of a file * @param {{ original: string, preview?: string, thumbnail?: string }} filepaths - * @param {{ mimeType: string, driveId: string }} metadata + * @param {{ mimeType: string }} metadata * @returns {Promise<{ driveId: string, name: string, type: 'photo' | 'video' | 'audio' }>} */ async create(filepaths, metadata) { @@ -82,7 +82,7 @@ export class BlobApi { } return { - driveId: metadata.driveId, + driveId: this.blobStore.writerDriveId, name, type: blobType, } @@ -95,7 +95,7 @@ export class BlobApi { * @param {string} metadata.mimeType */ async writeFile(filepath, { name, variant, type }, metadata) { - // @ts-ignore TODO: address blobStore.createWriteStream return type + // @ts-ignore TODO: return value types don't match pipeline's expectations, though they should await pipeline( fs.createReadStream(filepath), this.blobStore.createWriteStream({ type, variant, name }, { metadata }) diff --git a/tests/blob-api.js b/tests/blob-api.js index d8cb9cf8..30c04457 100644 --- a/tests/blob-api.js +++ b/tests/blob-api.js @@ -105,7 +105,7 @@ test('create blobs', async (t) => { } ) - t.is(attachment.driveId, '1234') + t.is(attachment.driveId, blobStore.writerDriveId) t.is(attachment.type, 'photo') t.teardown(async () => { From 13d6e53c5e92f539828d9576fd32f04b8f293e29 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Thu, 31 Aug 2023 08:37:37 -0700 Subject: [PATCH 12/13] fix additional driveId usage in blob-api.js --- src/blob-api.js | 4 ++-- tests/blob-api.js | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/blob-api.js b/src/blob-api.js index 6e776deb..3331aa35 100644 --- a/src/blob-api.js +++ b/src/blob-api.js @@ -28,9 +28,9 @@ export class BlobApi { * @returns {Promise} */ async getUrl(blobId) { - const { driveId, type, variant, name } = blobId + const { type, variant, name } = blobId const port = await getPort(this.blobServer.server) - return `http://127.0.0.1:${port}/${this.projectId}/${driveId}/${type}/${variant}/${name}` + return `http://127.0.0.1:${port}/${this.projectId}/${this.blobStore.writerDriveId}/${type}/${variant}/${name}` } /** diff --git a/tests/blob-api.js b/tests/blob-api.js index 30c04457..0d16a8c6 100644 --- a/tests/blob-api.js +++ b/tests/blob-api.js @@ -50,7 +50,6 @@ test('get port after listening event with unset port', async (t) => { test('get url from blobId', async (t) => { const projectId = '1234' - const driveId = '1234' const type = 'image' const variant = 'original' const name = '1234' @@ -65,13 +64,13 @@ test('get url from blobId', async (t) => { }) }) - const url = await blobApi.getUrl({ driveId, type, variant, name }) + const url = await blobApi.getUrl({ type, variant, name }) t.is( url, `http://127.0.0.1:${ blobServer.server.address().port - }/${projectId}/${driveId}/${type}/${variant}/${name}` + }/${projectId}/${blobStore.writerDriveId}/${type}/${variant}/${name}` ) t.teardown(async () => { await blobServer.close() @@ -100,7 +99,6 @@ test('create blobs', async (t) => { thumbnail: join(directory, 'thumbnail.png'), }, { - driveId: '1234', mimeType: 'image/png', } ) From 5a6e7b2a74eb3d6fc97752ee0aa041d5b0de4cc6 Mon Sep 17 00:00:00 2001 From: sethvincent Date: Thu, 31 Aug 2023 11:48:35 -0700 Subject: [PATCH 13/13] revert mistake --- src/blob-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blob-api.js b/src/blob-api.js index 3331aa35..6e776deb 100644 --- a/src/blob-api.js +++ b/src/blob-api.js @@ -28,9 +28,9 @@ export class BlobApi { * @returns {Promise} */ async getUrl(blobId) { - const { type, variant, name } = blobId + const { driveId, type, variant, name } = blobId const port = await getPort(this.blobServer.server) - return `http://127.0.0.1:${port}/${this.projectId}/${this.blobStore.writerDriveId}/${type}/${variant}/${name}` + return `http://127.0.0.1:${port}/${this.projectId}/${driveId}/${type}/${variant}/${name}` } /**