Skip to content

Commit 05e2cb5

Browse files
tomasciccolaTomás Ciccola
andauthored
feat: import config metadata and import to projectSettings (#721)
* * use new tables for `projectSettings` * import metadata from config * start solving tests * add metadata to every config make loading of config files sequential * add metadata specific unit tests * fix a type error * add mapeo-mock-data with mapeo-schema updated * re-generate drizzle, add `configMetadata` to `configImport` * update metadata.json on fixtures, fix wrong type on validation * import date should not exist on config file, but added on import * add local version of default-config * add parsing and validation of `fileVersion` which is a COMVER string now * revert use of iterpal * missed conflict on drizzle journal and snapshot * another missed conflict... * remove locally installed versions of dependencies * config-import: `importDate` should be defined when calling import, not when calling `get metadata` * add updated config fixtures, update @mapeo/default config, fix config.fileVersion parsing * remove unnecessary @ts-ignore * add metadata to config fixtures * missing commitiing metadata.json to validConfig * add metadata.json to completeConfig * delete all zips (don't know how they got here...) * recover missing metadata fixtures, remove unnecessary tests --------- Co-authored-by: Tomás Ciccola <tciccola@digital-democracy.com>
1 parent 1c097ee commit 05e2cb5

31 files changed

Lines changed: 371 additions & 25 deletions

File tree

drizzle/client/0000_organic_bloodstrike.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ CREATE TABLE `projectSettings` (
3030
`forks` text NOT NULL
3131
);
3232
--> statement-breakpoint
33-
CREATE UNIQUE INDEX `localDeviceInfo_deviceId_unique` ON `localDeviceInfo` (`deviceId`);
33+
CREATE UNIQUE INDEX `localDeviceInfo_deviceId_unique` ON `localDeviceInfo` (`deviceId`);

drizzle/client/meta/0000_snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,4 @@
196196
"tables": {},
197197
"columns": {}
198198
}
199-
}
199+
}

drizzle/client/meta/_journal.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
"breakpoints": true
1111
}
1212
]
13-
}
13+
}

drizzle/project/meta/0000_snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1141,4 +1141,4 @@
11411141
"tables": {},
11421142
"columns": {}
11431143
}
1144-
}
1144+
}

drizzle/project/meta/_journal.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
"breakpoints": true
1111
}
1212
]
13-
}
13+
}

package-lock.json

Lines changed: 17 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"homepage": "https://github.com/digidem/mapeo-core#readme",
108108
"devDependencies": {
109109
"@bufbuild/buf": "^1.26.1",
110-
"@mapeo/default-config": "4.0.0-alpha.5",
110+
"@mapeo/default-config": "4.0.0-alpha.6",
111111
"@mapeo/mock-data": "^1.0.3-alpha.1",
112112
"@sinonjs/fake-timers": "^10.0.2",
113113
"@types/b4a": "^1.6.0",

src/config-import.js

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { json, buffer } from 'node:stream/consumers'
44
import { assert } from './utils.js'
55
import path from 'node:path'
66
import { parse as parseBCP47 } from 'bcp-47'
7+
import { SUPPORTED_CONFIG_VERSION } from './constants.js'
78

89
// Throw error if a zipfile contains more than 10,000 entries
910
const MAX_ENTRIES = 10_000
@@ -26,6 +27,8 @@ const MAX_ICON_SIZE = 10_000_000
2627
* }} TranslationsFile
2728
*/
2829

30+
/** @typedef {NonNullable<import('@mapeo/schema').ProjectSettingsValue['configMetadata']>} MetadataFile */
31+
2932
/**
3033
* @typedef {Parameters<import('./icon-api.js').IconApi['create']>[0]} IconData
3134
*/
@@ -36,23 +39,31 @@ const MAX_ICON_SIZE = 10_000_000
3639
export async function readConfig(configPath) {
3740
/** @type {Error[]} */
3841
const warnings = []
42+
const importDate = new Date().toISOString()
3943

4044
const zip = await yauzl.open(configPath)
4145
if (zip.entryCount > MAX_ENTRIES) {
4246
// MAX_ENTRIES in MAC can be inacurrate
4347
throw new Error(`Zip file contains too many entries. Max is ${MAX_ENTRIES}`)
4448
}
4549
const entries = await zip.readEntries(MAX_ENTRIES)
46-
const [presetsFile, translationsFile] = await Promise.all([
47-
findPresetsFile(entries),
48-
findTranslationsFile(entries),
49-
])
50+
const presetsFile = await findPresetsFile(entries)
51+
const translationsFile = await findTranslationsFile(entries)
52+
const metadataFile = await findMetadataFile(entries)
53+
assert(
54+
isValidConfigFile(metadataFile),
55+
`invalid or missing config file version ${metadataFile.fileVersion}. We support version ${SUPPORTED_CONFIG_VERSION}}`
56+
)
5057

5158
return {
5259
get warnings() {
5360
return warnings
5461
},
5562

63+
get metadata() {
64+
return { ...metadataFile, importDate }
65+
},
66+
5667
async close() {
5768
zip.close()
5869
},
@@ -266,6 +277,27 @@ async function findTranslationsFile(entries) {
266277
return result
267278
}
268279

280+
/**
281+
* @param {ReadonlyArray<Entry>} entries
282+
* @returns {Promise<Omit<MetadataFile, 'importDate'>>}
283+
*/
284+
async function findMetadataFile(entries) {
285+
const metadataEntry = entries.find(
286+
(entry) => entry.filename === 'metadata.json'
287+
)
288+
assert(metadataEntry, 'Zip file does not contain metadata.json')
289+
let result
290+
try {
291+
result = await json(await metadataEntry.openReadStream())
292+
} catch (err) {
293+
throw new Error('Could not parse metadata.json')
294+
}
295+
assert(isRecord(result), 'Invalid metadata.json file')
296+
assert(isValidMetadataFile(result), 'Invalid structure of metadata file')
297+
298+
return result
299+
}
300+
269301
/**
270302
* @param {Error[]} warnings
271303
*/
@@ -487,6 +519,22 @@ function parseIcon(filename, buf) {
487519
}
488520
}
489521

522+
/**
523+
* @param {Record<string,unknown>} obj
524+
* @returns {obj is Omit<MetadataFile, 'importDate'>}
525+
*/
526+
function isValidMetadataFile(obj) {
527+
// extra fields are valid
528+
return (
529+
'name' in obj &&
530+
'buildDate' in obj &&
531+
'fileVersion' in obj &&
532+
typeof obj['name'] === 'string' &&
533+
typeof obj['buildDate'] === 'string' &&
534+
typeof obj['fileVersion'] === 'string'
535+
)
536+
}
537+
490538
/**
491539
* @param {Record<string, unknown>} message
492540
* @returns {message is Record<string,{label:string, value:string}>}
@@ -512,3 +560,21 @@ function isRecord(value) {
512560
function hasOwn(obj, prop) {
513561
return Object.prototype.hasOwnProperty.call(obj, prop)
514562
}
563+
564+
/**
565+
* @param {Object} obj
566+
* @param {string | undefined} [obj.fileVersion]
567+
* @returns {boolean}
568+
*/
569+
function isValidConfigFile({ fileVersion }) {
570+
if (!fileVersion) return false
571+
const regex = /^(\d+)\.(\d+)$/
572+
const match = fileVersion.match(regex)
573+
574+
if (!match) return false
575+
576+
const major = parseInt(match[1], 10)
577+
//const minor = parseInt(match[2], 10)
578+
579+
return major >= SUPPORTED_CONFIG_VERSION
580+
}

src/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ export const NAMESPACE_SCHEMAS = /** @type {const} */ ({
2020
],
2121
auth: ['coreOwnership', 'role'],
2222
})
23+
24+
export const SUPPORTED_CONFIG_VERSION = 1

src/mapeo-project.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { readConfig } from './config-import.js'
5151
import TranslationApi from './translation-api.js'
5252

5353
/** @typedef {Omit<import('@mapeo/schema').ProjectSettingsValue, 'schemaName'>} EditableProjectSettings */
54+
/** @typedef {import('@mapeo/schema').ProjectSettingsValue['configMetadata']} ConfigMetadata */
5455

5556
const CORESTORE_STORAGE_FOLDER_NAME = 'corestore'
5657
const INDEXER_STORAGE_FOLDER_NAME = 'indexer'
@@ -838,6 +839,7 @@ export class MapeoProject extends TypedEmitter {
838839
vertex: [],
839840
relation: [],
840841
},
842+
configMetadata: config.metadata,
841843
})
842844
this.#loadingConfig = false
843845
return config.warnings

0 commit comments

Comments
 (0)