diff --git a/.gitignore b/.gitignore index 95b0444..678818c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ testbed/build packages/database/build tools/upload/build packages/exif-js/build +packages/defs/build diff --git a/backend/package.json b/backend/package.json index 3fcd878..8363b50 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,11 +8,14 @@ "sd": "pnpm run start:dev", "start": "node build/index.js", "start:prod": "pnpm run clean-uploads && pnpm run copy-no-assets && cross-env NODE_ENV=production PORT=3000 ts-node-dev --respawn src/index.ts", - "start:dev": "pnpm run clean-uploads && pnpm run copy-50-assets && cross-env NODE_ENV=development pnpm run start-watch", - "start:dev-no-assets": "pnpm run clean-uploads && pnpm run copy-no-assets && cross-env NODE_ENV=development pnpm run start-watch", - "start:dev-1-asset": "pnpm run clean-uploads && pnpm run copy-1-asset && cross-env NODE_ENV=development pnpm run start-watch", - "start-watch": "cross-env NODE_ENV=development PORT=3000 ts-node-dev --respawn src/index.ts", + "start:dev": "pnpm run clean-uploads && pnpm run copy-50-assets && concurrently --names \"db,be\" \"npm run start-db-50-assets\" \"npm run start-watch\"", + "start:dev-no-assets": "pnpm run clean-uploads && pnpm run copy-no-assets && concurrently --names \"db,be\" \"npm run start-db-no-assets\" \"npm run start-watch\"", + "start:dev-1-asset": "pnpm run clean-uploads && pnpm run copy-1-asset && concurrently --names \"db,be\" \"npm run start-db-1-asset\" \"npm run start-watch\"", + "start-watch": "cross-env DB_CONNECTION_STRING=mongodb://localhost:7001 NODE_ENV=development PORT=3000 ts-node-dev --respawn src/index.ts", "start-for-e2e-tests": "pnpm run clean-uploads && pnpm run start:dev-no-assets", + "start-db-50-assets": "insta-mongo --db-port=7001 --rest-port=7000 --load=50-assets --fixtures=../fixtures --db=photosphere", + "start-db-no-assets": "insta-mongo --db-port=7001 --rest-port=7000 --load=no-assets --fixtures=../fixtures --db=photosphere", + "start-db-1-asset": "insta-mongo --db-port=7001 --rest-port=7000 --load=1-asset --fixtures=../fixtures --db=photosphere", "build": "pnpm install --production=false && pnpm run compile", "c": "pnpm run compile", "cw": "pnpm run compile:watch", @@ -39,7 +42,8 @@ "@types/node": "^18.19.15", "axios": "^0.27.2", "concurrently": "^8.2.2", - "cross-env": "^7.0.3", + "cross-env": "^7.0.3", + "insta-mongo": "^0.0.6", "jest": "^29.4.1", "nodemon": "^2.0.16", "supertest": "^6.3.3", @@ -54,6 +58,7 @@ "express": "^5.0.0-beta.1", "express-oauth2-jwt-bearer": "^1.6.0", "fs-extra": "^11.1.0", - "database": "workspace:*" + "defs": "workspace:*", + "mongodb": "^4.7.0" } } diff --git a/backend/src/index.ts b/backend/src/index.ts index 4e42918..055ee52 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,23 +1,33 @@ -import { StorageDatabaseCollection, StorageDatabases, StorageDirectory } from "database"; +import { MongoClient } from "mongodb"; import { createServer } from "./server"; import { CloudStorage } from "./services/cloud-storage"; import { FileStorage } from "./services/file-storage"; async function main() { + const dbName = "photosphere"; + const PORT = process.env.PORT; if (!PORT) { throw new Error(`Set environment variable PORT.`); } + const DB_CONNECTION_STRING = process.env.DB_CONNECTION_STRING; + if (DB_CONNECTION_STRING === undefined) { + throw new Error(`Set environment variable DB_CONNECTION_STRING.`); + } + + const client = new MongoClient(DB_CONNECTION_STRING); + await client.connect(); + + const db = client.db(dbName); + console.log(`Running in mode: ${process.env.NODE_ENV} on port ${PORT}.`); const storage = process.env.NODE_ENV === "production" ? new CloudStorage() : new FileStorage(); - const userCollection = new StorageDatabaseCollection(storage, `users`); - const databases = new StorageDatabases(new StorageDirectory(storage, `collections`)); - const app = await createServer(() => new Date(Date.now()), databases, userCollection, storage); + const app = await createServer(() => new Date(Date.now()), db, storage); app.listen(PORT, () => { console.log(`Photosphere listening on port ${PORT}`); }); diff --git a/backend/src/lib/asset.ts b/backend/src/lib/asset.ts deleted file mode 100644 index 9c19f88..0000000 --- a/backend/src/lib/asset.ts +++ /dev/null @@ -1,74 +0,0 @@ -// -// Represents an asset that has been uploaded to the backend. -// - -// -// Full asset data. -// -export interface IAsset { - - // - // Unique ID of the asset in the database. - // - _id: string; - - // - // The original name of the asset before it was uploaded. - // - origFileName: string; - - // - // Width of the image or video. - // - width: number; - - // - // Height of the image or video. - // - height: number; - - // - // Hash of the asset. - // - hash: string; - - // - // Optional reverse geocoded location for the asset. - // - location?: string; - - // - // The date the file was created. - // - fileDate: Date; - - // - // The date the photo was taken, if known. - // - photoDate?: Date; - - // - // Date by which to sort the asset. - // - sortDate: Date; - - // - /// The date the asset was uploaded. - // - uploadDate: Date; - - // - // Optional extra properties for the asset, like exif data. - // - properties?: any; - - // - // Labels attached to the asset. - // - labels?: string[]; - - // - // Description of the asset, once the user has set it. - // - description?: string; -} \ No newline at end of file diff --git a/packages/database/src/lib/storage/storage.ts b/backend/src/lib/storage/storage.ts similarity index 100% rename from packages/database/src/lib/storage/storage.ts rename to backend/src/lib/storage/storage.ts diff --git a/backend/src/lib/user.ts b/backend/src/lib/user.ts index cfe0ab2..9063a3e 100644 --- a/backend/src/lib/user.ts +++ b/backend/src/lib/user.ts @@ -22,6 +22,11 @@ export interface ICollections { // Defines a user. // export interface IUser { + // + // The user id. + // + _id: string; + // // Metadata for the user's collections. // diff --git a/packages/database/src/lib/uuid.ts b/backend/src/lib/uuid.ts similarity index 100% rename from packages/database/src/lib/uuid.ts rename to backend/src/lib/uuid.ts diff --git a/backend/src/server.ts b/backend/src/server.ts index af7d7e4..20459c5 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -2,8 +2,10 @@ import express, { Request, Response } from "express"; import cors from "cors"; import { auth } from "express-oauth2-jwt-bearer"; import { IUser } from "./lib/user"; -import { IDatabaseCollection, IDatabaseOp, IDatabaseOpRecord, IDatabases, IHashRecord, IStorage, applyOperationToDb, getJournal } from "database"; -import { IAsset } from "./lib/asset"; +import { Db } from "mongodb"; +import { IStorage } from "./lib/storage/storage"; +import { IDatabaseOp, IDatabaseOpRecord } from "defs"; +import { uuid } from "./lib/uuid"; declare global { namespace Express { @@ -17,7 +19,7 @@ declare global { // // Starts the REST API. // -export async function createServer(now: () => Date, databases: IDatabases, userCollection: IDatabaseCollection, storage: IStorage) { +export async function createServer(now: () => Date, db: Db, storage: IStorage) { const app = express(); app.use(cors()); @@ -58,7 +60,6 @@ export async function createServer(now: () => Date, databases: IDatabases, userC // Mocks a JWT token. // app.use((req, res, next) => { - req.auth = { payload: { sub: "test-user", // Test user. @@ -83,7 +84,7 @@ export async function createServer(now: () => Date, databases: IDatabases, userC // Removes the auth0| prefix the user id. userId = userId.substring(6); } - const user = await userCollection.getOne(userId); + const user = await db.collection("users").findOne({ _id: userId }); if (!user) { console.log(`User not found: ${userId}`); res.sendStatus(401); @@ -154,19 +155,63 @@ export async function createServer(now: () => Date, databases: IDatabases, userC // // Gets the user's metadata. // - app.get("/user", asyncErrorHandler(async (req, res) => { //todo: test me .http and jest + app.get("/user", asyncErrorHandler(async (req, res) => { res.json(req.user); })); - + // // Applies a set of operations to the asset database. // app.post("/operations", express.json(), asyncErrorHandler(async (req, res) => { const ops = getValue(req.body, "ops"); const clientId = getValue(req.body, "clientId"); + let sequence = 0; for (const op of ops) { - const assetCollection = await databases.database(op.databaseName); - await applyOperationToDb(assetCollection, op, clientId); + const databaseOpRecord: IDatabaseOpRecord = { + _id: uuid(), + serverTime: new Date(), + sequence, + clientId, + collectionName: op.collectionName, + recordId: op.recordId, + op: op.op, + }; + + sequence += 1; + + const journalCollection = db.collection("journal"); + await journalCollection.insertOne(databaseOpRecord); + + const recordCollection = db.collection(op.collectionName); + if (op.op.type === "set") { + await recordCollection.updateOne( + { _id: op.recordId }, + { $set: op.op.fields }, + { upsert: true } + ); + } + else if (op.op.type === "push") { + await recordCollection.updateOne( + { _id: op.recordId }, + { + $push: { + [op.op.field]: op.op.value, + }, + }, + { upsert: true } + ); + } + else if (op.op.type === "pull") { + await recordCollection.updateOne( + { _id: op.recordId }, + { + $pull: { + [op.op.field]: op.op.value, + }, + }, + { upsert: true } + ); + } } res.sendStatus(200); })); @@ -175,44 +220,54 @@ export async function createServer(now: () => Date, databases: IDatabases, userC // Gets the journal of operations that have been applied to the database. // app.post("/journal", express.json(), asyncErrorHandler(async (req, res) => { - const collectionId = getValue(req.body, "collectionId"); const clientId = getValue(req.body, "clientId"); - const lastUpdateId = req.body.lastUpdateId; - const assetCollection = await databases.database(collectionId); - const result = await getJournal(assetCollection, clientId, lastUpdateId); - res.json(result); + let lastUpdateTime = req.body.lastUpdateTime; + if (lastUpdateTime !== undefined) { + lastUpdateTime = new Date(lastUpdateTime); + } + + const journalCollection = db.collection("journal"); + let query: any = {}; + if (lastUpdateTime !== undefined) { + query.serverTime = { $gt: lastUpdateTime }; + } + + const serverTimeNow = new Date(); + let journalRecords = await journalCollection.find(query) + .sort({ serverTime: 1, sequence: 1 }) + .toArray(); //TODO: Want pagination. + + //TODO: Filter collections the user doesn't have access to. + + // + // Filter out records from the same client. + // + journalRecords = journalRecords.filter((record) => record.clientId !== clientId); + + res.json({ + journalRecords, + latestTime: serverTimeNow, + }); })); // // Retreives the latest update id for a collection. // - app.get("/latest-update-id", asyncErrorHandler(async (req, res) => { - const collectionId = getHeader(req, "col"); - const assetCollection = await databases.database(collectionId); - const journalCollection = assetCollection.collection("journal"); - const journalIdsPage = await journalCollection.listAll(1); - if (journalIdsPage.records.length === 0) { - res.json({ - latestUpdateId: undefined, - }); - return - } - - const latestUpdateId = journalIdsPage.records[0]; + app.get("/latest-time", asyncErrorHandler(async (req, res) => { + const serverTimeNow = new Date(); res.json({ - latestUpdateId: latestUpdateId, + latestTime: serverTimeNow, }); })); - // // Uploads a new asset. // app.post("/asset", asyncErrorHandler(async (req, res) => { const assetId = getHeader(req, "id"); - const collectionId = getHeader(req, "col"); + const setId = getHeader(req, "set"); const contentType = getHeader(req, "content-type"); const assetType = getHeader(req, "asset-type"); - await storage.writeStream(`collections/${collectionId}/${assetType}`, assetId, contentType, req); + await storage.writeStream(`collections/${setId}/${assetType}`, assetId, contentType, req); res.sendStatus(200); })); @@ -220,16 +275,15 @@ export async function createServer(now: () => Date, databases: IDatabases, userC // Gets a particular asset by id. // app.get("/asset", asyncErrorHandler(async (req, res) => { - const assetId = req.query.id as string; - const collectionId = req.query.col as string; + const setId = req.query.set as string; const assetType = req.query.type as string; - if (!assetId || !collectionId || !assetType) { + if (!assetId || !setId || !assetType) { res.sendStatus(400); return; } - const info = await storage.info(`collections/${collectionId}/${assetType}`, assetId); + const info = await storage.info(`collections/${setId}/${assetType}`, assetId); if (!info) { res.sendStatus(404); return; @@ -239,34 +293,18 @@ export async function createServer(now: () => Date, databases: IDatabases, userC "Content-Type": info.contentType, }); - storage.readStream(`collections/${collectionId}/${assetType}`, assetId) + storage.readStream(`collections/${setId}/${assetType}`, assetId) .pipe(res); })); - // - // Sets a record in the database. - // - app.get("/set-one", asyncErrorHandler(async (req, res) => { - const databaseName = getValue(req.body, "databaseName"); - const collectionName = getValue(req.body, "collectionName"); - const recordId = getValue(req.body, "recordId"); - const record = getValue(req.body, "record"); - const collection = databases.database(databaseName); - const dbCollection = collection.collection(collectionName); - await dbCollection.setOne(recordId, record); - res.sendStatus(200); - })); - // // Gets a record from the database. // app.get("/get-one", asyncErrorHandler(async (req, res) => { - const databaseName = getValue(req.query, "db"); const collectionName = getValue(req.query, "col"); const recordId = getValue(req.query, "id"); - const collection = databases.database(databaseName); - const dbCollection = collection.collection(collectionName); - const record = await dbCollection.getOne(recordId); + const collection = db.collection(collectionName); + const record = await collection.findOne({ _id: recordId }); if (!record) { res.sendStatus(404); return; @@ -275,28 +313,17 @@ export async function createServer(now: () => Date, databases: IDatabases, userC res.json(record); })); - // - // Lists all records in the database. - // - app.get("/list-all", asyncErrorHandler(async (req, res) => { - const databaseName = getValue(req.query, "db"); - const collectionName = getValue(req.query, "col"); - const max = getIntQueryParam(req, "max"); - const next = req.query.next as string; - const collection = databases.database(databaseName); - const dbCollection = collection.collection(collectionName); - const page = await dbCollection.listAll(max, next); - res.json(page); - })); - // // Gets all records in the database. // app.get("/get-all", asyncErrorHandler(async (req, res) => { - const databaseName = getValue(req.query, "db"); + const setId = getValue(req.query, "set"); const collectionName = getValue(req.query, "col"); - const max = getIntQueryParam(req, "max"); - const next = req.query.next as string; + const skip = getIntQueryParam(req, "skip"); + const limit = getIntQueryParam(req, "limit"); + + //TODO: black list/white list collections the client access access. + //TODO: Ensure the user can access the set. // // TODO: bring this online later. @@ -314,23 +341,13 @@ export async function createServer(now: () => Date, databases: IDatabases, userC // return; // } - const collection = databases.database(databaseName); - const dbCollection = collection.collection(collectionName); - const page = await dbCollection.getAll(max, next); - res.json(page); - })); + const collection = db.collection(collectionName); + const records = await collection.find({ setId }) + .skip(skip) + .limit(limit) + .toArray(); - // - // Deletes a record from the database. - // - app.get("/delete-one", asyncErrorHandler(async (req, res) => { - const databaseName = getValue(req.query, "databaseName"); - const collectionName = getValue(req.query, "collectionName"); - const recordId = getValue(req.query, "recordId"); - const collection = databases.database(databaseName); - const dbCollection = collection.collection(collectionName); - await dbCollection.deleteOne(recordId); - res.sendStatus(200); + res.json(records); })); return app; diff --git a/backend/src/services/cloud-storage.ts b/backend/src/services/cloud-storage.ts index 3b66203..f5b0202 100644 --- a/backend/src/services/cloud-storage.ts +++ b/backend/src/services/cloud-storage.ts @@ -1,6 +1,6 @@ -import { IFileInfo, IListResult, IStorage } from "database"; import { Readable } from "stream"; import aws from "aws-sdk"; +import { IFileInfo, IListResult, IStorage } from "../lib/storage/storage"; /* AWS S3: diff --git a/backend/src/services/file-storage.ts b/backend/src/services/file-storage.ts index 8bab215..f177b0a 100644 --- a/backend/src/services/file-storage.ts +++ b/backend/src/services/file-storage.ts @@ -1,7 +1,7 @@ import * as fs from "fs-extra"; import { join, dirname } from "path"; -import { IFileInfo, IListResult, IStorage } from "database"; import { Readable } from "stream"; +import { IFileInfo, IListResult, IStorage } from "../lib/storage/storage"; export class FileStorage implements IStorage { diff --git a/backend/src/test/server.test.ts b/backend/src/test/server.test.ts index 80a7da6..d28d12a 100644 --- a/backend/src/test/server.test.ts +++ b/backend/src/test/server.test.ts @@ -9,7 +9,7 @@ import http, { IncomingMessage } from "http"; describe("photosphere backend", () => { const dateNow = dayjs("2023-02-08T01:27:01.419Z").toDate(); - const collectionId = "automated-tests-collection"; + const setId = "automated-tests-collection"; let servers: http.Server[] = []; @@ -18,13 +18,48 @@ describe("photosphere backend", () => { // async function initServer() { - const mockUserCollection: any = { - getOne: () => ({ - }), + const mockUser = { + _id: "test-user", + sets: { + upload: "upload", + default: "default", + access: [ "access" ], + }, }; - const mockDatabases: any = {}; + const mockStorage: any = {}; - const app = await createServer(() => dateNow, mockDatabases, mockUserCollection, mockStorage); + const mockUsersCollection = { + findOne: async () => mockUser, + }; + const mockJournalCollection = { + insertOne: jest.fn(), + }; + const mockMetadataCollection = { + updateOne: jest.fn(), + find: () => ({ + skip: () => ({ + limit: () => ({ + toArray: async () => [], + }), + }), + }), + }; + const collections: any = { + users: mockUsersCollection, + metadata: mockMetadataCollection, + journal: mockJournalCollection, + }; + const mockDatabase: any = { + collection: (name: string) => { + const collection = collections[name]; + if (!collection) { + throw new Error(`No mock collection for ${name}`); + } + return collection; + }, + }; + + const app = await createServer(() => dateNow, mockDatabase, mockStorage); const server = app.listen(); servers.push(server); @@ -32,7 +67,16 @@ describe("photosphere backend", () => { const address = server.address() as AddressInfo; const baseUrl = `http://localhost:${address.port}`; - return { app, server, baseUrl, mockStorage, mockDatabases, mockUserCollection }; + return { + app, + server, + baseUrl, + mockDatabase, + mockStorage, + mockUser, + mockJournalCollection, + mockMetadataCollection, + }; } beforeAll(() => { @@ -68,17 +112,7 @@ describe("photosphere backend", () => { } test("user metadata", async () => { - const { baseUrl, mockUserCollection: mockUserDatabase } = await initServer(); - - const mockUser = { - _id: "test-user", - collections: { - upload: "upload", - default: "default", - access: [ "access" ], - }, - }; - mockUserDatabase.getOne = async () => mockUser; + const { baseUrl, mockUser } = await initServer(); const response = await axios.get(`${baseUrl}/user`); expect(response.status).toBe(200); @@ -86,40 +120,33 @@ describe("photosphere backend", () => { }); test("no assets", async () => { - const { baseUrl, mockDatabases } = await initServer(); - - const mockCollection: any = { - getAll: async () => ({ records: [] }), - }; - const mockDatabase: any = { - collection: () => mockCollection, - }; - mockDatabases.database = async () => mockDatabase; - - const response = await axios.get(`${baseUrl}/assets?col=${collectionId}`); + const { baseUrl } = await initServer(); + const response = await axios.get(`${baseUrl}/get-all?set=${setId}&col=metadata&skip=0&limit=100`); expect(response.status).toBe(200); - expect(response.data).toEqual({ assets: [] }); + expect(response.data).toEqual([]); }); test("upload asset metadata", async () => { - const { baseUrl, mockDatabases } = await initServer(); + const { baseUrl, mockMetadataCollection, mockJournalCollection } = await initServer(); - const mockCollection: any = { - getOne: async () => undefined, - setOne: jest.fn(), - }; - const mockDatabase: any = { - collection: () => mockCollection, - }; - mockDatabases.database = async () => mockDatabase; + //fio: + // const mockCollection: any = { + // getOne: async () => undefined, + // setOne: jest.fn(), + // }; + // const mockDatabase: any = { + // collection: () => mockCollection, + // }; + //todo: + // mockDatabases.database = async () => mockDatabase; const assetId = "1234"; const hash = "ACBD"; const metadata = { _id: assetId, - col: collectionId, + col: setId, fileName: "a-test-file.jpg", contentType: "image/jpeg", width: 256, @@ -139,8 +166,8 @@ describe("photosphere backend", () => { const response = await axios.post(`${baseUrl}/operations`, { clientId: "test-client", ops: [{ - databaseName: collectionId, collectionName: "metadata", + setId, recordId: assetId, op: { type: "set", @@ -151,23 +178,27 @@ describe("photosphere backend", () => { expect(response.status).toBe(200); - expect(mockCollection.setOne).toHaveBeenCalledTimes(2); - expect(mockCollection.setOne).toHaveBeenCalledWith(expect.any(String), { - clientId: "test-client", - collectionName: "metadata", - op: { - fields: { - ...metadata, - - }, - type: "set" - }, - recordId: "1234", - serverTime: expect.any(String), - }); - expect(mockCollection.setOne).toHaveBeenCalledWith(assetId, { - ...metadata, - }); + expect(mockMetadataCollection.updateOne).toHaveBeenCalledTimes(1); + expect(mockMetadataCollection.updateOne).toHaveBeenCalledWith( + { _id: assetId }, + { $set: metadata }, + { upsert: true } + ); + expect(mockJournalCollection.insertOne).toHaveBeenCalledTimes(1); + expect(mockJournalCollection.insertOne).toHaveBeenCalledWith( + expect.objectContaining({ + _id: expect.any(String), + serverTime: expect.any(String), + sequence: expect.any(Number), + clientId: "test-client", + collectionName: "metadata", + recordId: assetId, + op: { + type: "set", + fields: metadata, + }, + }) + ); }); test("upload asset data", async () => { @@ -185,7 +216,7 @@ describe("photosphere backend", () => { fs.readFileSync("./test/test-assets/1.jpeg"), { headers: { - "col": collectionId, + "col": setId, "id": assetId, "Content-Type": contentType, "asset-type": assetType, @@ -197,7 +228,7 @@ describe("photosphere backend", () => { expect(mockStorage.writeStream).toHaveBeenCalledTimes(1); expect(mockStorage.writeStream).toHaveBeenCalledWith( - `collections/${collectionId}/${assetType}`, assetId, contentType, expect.any(IncomingMessage) + `collections/${setId}/${assetType}`, assetId, contentType, expect.any(IncomingMessage) ); }); @@ -211,7 +242,7 @@ describe("photosphere backend", () => { mockStorage.info = async () => ({ contentType }); mockStorage.readStream = () => stringStream(content); - const response = await axios.get(`${baseUrl}/asset?id=${assetId}&col=${collectionId}&type=original`); + const response = await axios.get(`${baseUrl}/asset?id=${assetId}&col=${setId}&type=original`); expect(response.status).toBe(200); expect(response.headers["content-type"]).toBe(contentType); expect(response.data).toEqual(content); @@ -224,7 +255,7 @@ describe("photosphere backend", () => { mockStorage.info = async () => undefined; - const response = await axios.get(`${baseUrl}/asset?id=${assetId}&col=${collectionId}&type=original`); + const response = await axios.get(`${baseUrl}/asset?id=${assetId}&col=${setId}&type=original`); expect(response.status).toBe(404); }); @@ -232,14 +263,14 @@ describe("photosphere backend", () => { const { baseUrl } = await initServer(); - const response = await axios.get(`${baseUrl}/asset?col=${collectionId}&type=original`); + const response = await axios.get(`${baseUrl}/asset?col=${setId}&type=original`); expect(response.status).toBe(400); }); test("check for existing asset by hash", async () => { const assetId = "1234"; - const { baseUrl, mockDatabases } = await initServer(); + const { baseUrl } = await initServer(); const mockCollection: any = { getOne: async () => [ assetId ], @@ -247,18 +278,16 @@ describe("photosphere backend", () => { const mockDatabase: any = { collection: () => mockCollection, }; - mockDatabases.database = async () => mockDatabase; - const hash = "ABCD"; - const response = await axios.get(`${baseUrl}/check-asset?hash=${hash}&col=${collectionId}`); + const response = await axios.get(`${baseUrl}/check-asset?hash=${hash}&col=${setId}`); expect(response.status).toBe(200); expect(response.data.assetId).toEqual(assetId); }); test("check for non-existing asset by hash", async () => { - const { baseUrl, mockDatabases } = await initServer(); + const { baseUrl } = await initServer(); const mockCollection: any = { getOne: async () => undefined, @@ -266,10 +295,9 @@ describe("photosphere backend", () => { const mockDatabase: any = { collection: () => mockCollection, }; - mockDatabases.database = async () => mockDatabase; const hash = "1234"; - const response = await axios.get(`${baseUrl}/check-asset?hash=${hash}&col=${collectionId}`); + const response = await axios.get(`${baseUrl}/check-asset?hash=${hash}&col=${setId}`); expect(response.status).toBe(200); expect(response.data.assetId).toBeUndefined(); }); @@ -278,13 +306,13 @@ describe("photosphere backend", () => { const { baseUrl } = await initServer(); - const response = await axios.get(`${baseUrl}/check-asset?col=${collectionId}`); + const response = await axios.get(`${baseUrl}/check-asset?col=${setId}`); expect(response.status).toBe(400); }); test("can get assets", async () => { - const { baseUrl, mockDatabases } = await initServer(); + const { baseUrl } = await initServer(); const mockAsset1: any = { contentType: "image/jpeg", @@ -300,9 +328,8 @@ describe("photosphere backend", () => { const mockDatabase: any = { collection: () => mockCollection, }; - mockDatabases.database = async () => mockDatabase; - const response = await axios.get(`${baseUrl}/assets?col=${collectionId}`); + const response = await axios.get(`${baseUrl}/assets?col=${setId}`); expect(response.status).toBe(200); expect(response.data).toEqual({ diff --git a/backend/test/backend.http b/backend/test/backend.http index 7a57805..408402a 100644 --- a/backend/test/backend.http +++ b/backend/test/backend.http @@ -1,10 +1,10 @@ @base_url = http://localhost:3000 # @base_url = https://photosphere-api.codecapers.com.au @token = not needed -@collection = test-collection -# @collection = test-collection -@collection = d5a75330-61ca-487d-b81b-0d2aeaa74c76 +@setId = test-collection +#@collection = d5a75330-61ca-487d-b81b-0d2aeaa74c76 @asset = 1234 +@hash_id = 4567 @hash = ABCD ### @@ -34,12 +34,12 @@ Authorization: Bearer {{token}} "clientId": "a-client-id", "ops": [ { - "databaseName": "{{collection}}", "collectionName": "metadata", "recordId": "{{asset}}", "op": { "type": "set", "fields": { + "setId": "{{setId}}", "contentType": "image/jpeg", "fileName": "./test-assets/1.jpeg", "width": 4160, @@ -58,13 +58,15 @@ Authorization: Bearer {{token}} } }, { - "databaseName": "{{collection}}", - "collectionName": "hashes", - "recordId": "{{hash}}", + "collectionName": "hashes", + "recordId": "{{hash_id}}", "op": { - "type": "push", - "field": "assetIds", - "value": "{{asset}}" + "type": "set", + "fields": { + "hash": "{{hash}}", + "assetId": "{{asset}}", + "setId": "{{setId}}" + } } } ] @@ -74,15 +76,14 @@ Authorization: Bearer {{token}} # Gets metadata for an asset. # -GET {{base_url}}/get-one?id={{asset}}&db={{collection}}&col=metadata +GET {{base_url}}/get-one?id={{asset}}&col=metadata Authorization: Bearer {{token}} ### # Gets hashes for an asset. - -GET {{base_url}}/get-one?id={{hash}}&db={{collection}}&col=hashes +GET {{base_url}}/get-one?id={{hash_id}}&col=hashes Authorization: Bearer {{token}} @@ -94,7 +95,7 @@ Authorization: Bearer {{token}} POST {{base_url}}/asset Content-Type: image/jpeg id: {{asset}} -col: {{collection}} +set: {{setId}} asset-type: original Authorization: Bearer {{token}} @@ -105,7 +106,7 @@ Authorization: Bearer {{token}} # Retrieve an asset. # -GET {{base_url}}/asset?id={{asset}}&col={{collection}}&tok={{token}}&type=original +GET {{base_url}}/asset?id={{asset}}&set={{setId}}&tok={{token}}&type=original ### # @@ -115,7 +116,7 @@ GET {{base_url}}/asset?id={{asset}}&col={{collection}}&tok={{token}}&type=origin POST {{base_url}}/asset Content-Type: image/jpeg id: {{asset}} -col: {{collection}} +set: {{setId}} asset-type: thumb Authorization: Bearer {{token}} @@ -125,7 +126,7 @@ Authorization: Bearer {{token}} # Retrieve an thumbnail. # -GET {{base_url}}/asset?id={{asset}}&col={{collection}}&tok={{token}}&type=thumb +GET {{base_url}}/asset?id={{asset}}&set={{setId}}&tok={{token}}&type=thumb ### # @@ -135,7 +136,7 @@ GET {{base_url}}/asset?id={{asset}}&col={{collection}}&tok={{token}}&type=thumb POST {{base_url}}/asset Content-Type: image/jpeg id: {{asset}} -col: {{collection}} +set: {{setId}} asset-type: display Authorization: Bearer {{token}} @@ -145,13 +146,13 @@ Authorization: Bearer {{token}} # Retrieve the display asset. # -GET {{base_url}}/asset?id={{asset}}&col={{collection}}&tok={{token}}&type=display +GET {{base_url}}/asset?id={{asset}}&set={{setId}}&tok={{token}}&type=display ### # Gets metadata for an asset. # -GET {{base_url}}/get-one?id={{asset}}&db={{collection}}&col=metadata +GET {{base_url}}/get-one?id={{asset}}&col=metadata Authorization: Bearer {{token}} ### @@ -165,7 +166,6 @@ Authorization: Bearer {{token}} { "clientId": "a-client-id", "ops": [{ - "databaseName": "{{collection}}", "collectionName": "metadata", "recordId": "{{asset}}", "op": { @@ -181,7 +181,7 @@ Authorization: Bearer {{token}} # Gets metadata for an asset. # -GET {{base_url}}/get-one?id={{asset}}&db={{collection}}&col=metadata +GET {{base_url}}/get-one?id={{asset}}&col=metadata Authorization: Bearer {{token}} ### @@ -195,7 +195,6 @@ Authorization: Bearer {{token}} { "clientId": "a-client-id", "ops": [{ - "databaseName": "{{collection}}", "collectionName": "metadata", "recordId": "{{asset}}", "op": { @@ -210,7 +209,7 @@ Authorization: Bearer {{token}} # Gets metadata for an asset. # -GET {{base_url}}/get-one?id={{asset}}&db={{collection}}&col=metadata +GET {{base_url}}/get-one?id={{asset}}&col=metadata Authorization: Bearer {{token}} ### @@ -224,7 +223,6 @@ Authorization: Bearer {{token}} { "clientId": "a-client-id", "ops": [{ - "databaseName": "{{collection}}", "collectionName": "metadata", "recordId": "{{asset}}", "op": { @@ -239,10 +237,11 @@ Authorization: Bearer {{token}} # Gets metadata for an asset. # -GET {{base_url}}/get-one?id={{asset}}&db={{collection}}&col=metadata +GET {{base_url}}/get-one?id={{asset}}&col=metadata Authorization: Bearer {{token}} ### +# @name get_journal # Gets journal operations for a collection. # @@ -251,36 +250,36 @@ Content-Type: application/json Authorization: Bearer {{token}} { - "collectionId": "{{collection}}", - "clientId": "a-client-id" + "collectionId": "{{setId}}", + "clientId": "a-different-client-id" } ### # Gets journal operations after a certain update. # -@last_update = 16 +@last_update_time = {get_journal.repsonse.body.latestTime} POST {{base_url}}/journal Content-Type: application/json Authorization: Bearer {{token}} { - "collectionId": "{{collection}}", + "collectionId": "{{setId}}", "clientId": "a-client-id", - "lastUpdateId": "{{last_update}}" + "lastUpdateTime": "{{last_update_time}}" } ### -# Lists a page of assets. +# Gets the latest server time. # -GET {{base_url}}/list-all?db={{collection}}&col=metadata&max=5 +GET {{base_url}}/latest-time Authorization: Bearer {{token}} ### # Gets a page of assets. # -GET {{base_url}}/get-all?db={{collection}}&col=metadata&max=5 +GET {{base_url}}/get-all?set={{setId}}&col=metadata&skip=0&limit=5 Authorization: Bearer {{token}} \ No newline at end of file diff --git a/electron/frontend/package.json b/electron/frontend/package.json index 65f7739..5cd4d8e 100644 --- a/electron/frontend/package.json +++ b/electron/frontend/package.json @@ -54,6 +54,6 @@ "react-dom": "^18.2.0", "react-router-dom": "^6.4.1", "user-interface": "workspace:*", - "database": "workspace:*" + "defs": "workspace:*" } } diff --git a/electron/frontend/src/app.tsx b/electron/frontend/src/app.tsx index 6008d1a..b88ae7b 100644 --- a/electron/frontend/src/app.tsx +++ b/electron/frontend/src/app.tsx @@ -1,39 +1,23 @@ import React, { useRef } from "react"; import { HashRouter } from "react-router-dom"; -import { ApiContextProvider, AuthContextProvider, DbSyncContextProvider, GalleryContextProvider, IndexeddbContextProvider, Main, UploadContextProvider, isProduction, useApi, useCloudGallerySink, useCloudGallerySource, useIndexeddb, useIndexeddbGallerySink, useIndexeddbGallerySource, useLocalGallerySink, useLocalGallerySource } from "user-interface"; +import { ApiContextProvider, AuthContextProvider, DbSyncContextProvider, GalleryContextProvider, IAssetUpdateRecord, IAssetUploadRecord, IndexeddbContextProvider, Main, PersistentQueue, UploadContextProvider, isProduction, useApi, useIndexeddb, useLocalGallerySink, useLocalGallerySource } from "user-interface"; import { Auth0Provider } from "@auth0/auth0-react"; -import { CloudDatabases, IAssetUpdateRecord, IAssetUploadRecord, PersistentQueue } from "database"; import { ComputerPage } from "./pages/computer"; import { ScanContextProvider } from "./context/scan-context"; import dayjs from "dayjs"; function GallerySetup() { - const api = useApi(); - const cloudDatabases = new CloudDatabases(api); - const indexeddb = useIndexeddb(); - const indexeddbSource = useIndexeddbGallerySource({ indexeddbDatabases: indexeddb.databases }); - const indexeddbSink = useIndexeddbGallerySink({ indexeddbDatabases: indexeddb.databases }); - - const cloudSource = useCloudGallerySource({ api }); - const cloudSink = useCloudGallerySink({ api }); - const userDatabase = indexeddb.databases.database("user"); const outgoingAssetUploadQueue = useRef>(new PersistentQueue(userDatabase, "outgoing-asset-upload")); const outgoingAssetUpdateQueue = useRef>(new PersistentQueue(userDatabase, "outgoing-asset-update")); - const localSource = useLocalGallerySource({ indexeddbSource, indexeddbSink, cloudSource }); - const localSink = useLocalGallerySink({ indexeddbSink, outgoingAssetUploadQueue: outgoingAssetUploadQueue.current, outgoingAssetUpdateQueue: outgoingAssetUpdateQueue.current }); + const localSource = useLocalGallerySource({ indexeddbDatabases: indexeddb.databases, api }); + const localSink = useLocalGallerySink({ outgoingAssetUploadQueue: outgoingAssetUploadQueue.current, outgoingAssetUpdateQueue: outgoingAssetUpdateQueue.current, indexeddbDatabases: indexeddb.databases }); return ( diff --git a/electron/frontend/src/context/scan-context.tsx b/electron/frontend/src/context/scan-context.tsx index 7b0d02f..9dde237 100644 --- a/electron/frontend/src/context/scan-context.tsx +++ b/electron/frontend/src/context/scan-context.tsx @@ -6,9 +6,9 @@ import React, { createContext, ReactNode, useContext, useRef, useState } from "r import { scanImages as _scanImages, getContentType } from "../lib/scan"; import dayjs from "dayjs"; import { loadFileInfo, loadFileToBlob, loadFileToThumbnail } from "../lib/file"; -import { IAsset, IAssetData, IAssetSource, IPage, uuid } from "database"; -import { useUpload } from "user-interface"; +import { IAssetData, IAssetSource, useUpload } from "user-interface"; import path from "path"; +import { IAsset } from "defs"; export interface IScanContext extends IAssetSource { // @@ -49,10 +49,12 @@ export function ScanContextProvider({ children }: IProps) { height: resolution.height, origFileName: fileDetails.path, origPath: "", + contentType: fileDetails.contentType, hash, fileDate: dayjs(fileDate).toISOString(), sortDate: dayjs(fileDate).toISOString(), uploadDate: dayjs().toISOString(), + setId: "this doesn't make sense here" }; assets.current.push(newAsset); @@ -77,11 +79,8 @@ export function ScanContextProvider({ children }: IProps) { // // Loads metadata for all assets. // - async function loadAssets(collectionId: string, max: number, next?: string): Promise> { - return { - records: next !== undefined ? assets.current.slice(parseInt(next), assets.current.length) : assets.current, - next: isScanning.current ? assets.current.length.toString() : undefined, - }; + async function loadAssets(collectionId: string): Promise { + return assets.current; } // @@ -122,7 +121,6 @@ export function ScanContextProvider({ children }: IProps) { } const value: IScanContext = { - isInitialised: true, scanImages, loadAssets, mapHashToAssets, diff --git a/fixtures/1-asset/files/collections/test-collection/journal/27 b/fixtures/1-asset/files/collections/test-collection/journal/27 deleted file mode 100644 index dc0fef8..0000000 --- a/fixtures/1-asset/files/collections/test-collection/journal/27 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef4d", - "op": { - "type": "set", - "fields": { - "origFileName": "7kpC3MZ5BOg.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5954, - "height": 3590, - "description": "snow covered mountain trees", - "location": "Comox Valley, BC, Canada", - "searchText": " snow covered mountain trees Comox Valley, BC, Canada", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS Rebel T7i", - "name": "Canon, EOS Rebel T7i", - "exposure_time": "1/125", - "aperture": "11.0", - "focal_length": "29.0", - "iso": 400 - } - }, - "hash": "80f447731e9107ec1499c90870b131d27f31b78d5cd1d4cc44e21805da924176", - "fileDate": "2023-02-07T21:57:22Z", - "photoDate": "2023-02-07T21:57:22Z" - } - } -} \ No newline at end of file diff --git a/fixtures/1-asset/files/collections/test-collection/metadata.json b/fixtures/1-asset/files/collections/test-collection/metadata.json deleted file mode 100644 index 7b91f2c..0000000 --- a/fixtures/1-asset/files/collections/test-collection/metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Test collection", - "owners": [ - "test-user" - ] -} \ No newline at end of file diff --git a/fixtures/1-asset/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4d b/fixtures/1-asset/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4d deleted file mode 100644 index 53b178d..0000000 --- a/fixtures/1-asset/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4d +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef4d", - "origFileName": "7kpC3MZ5BOg.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5954, - "height": 3590, - "description": "snow covered mountain trees", - "location": "Comox Valley, BC, Canada", - "searchText": " snow covered mountain trees Comox Valley, BC, Canada", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS Rebel T7i", - "name": "Canon, EOS Rebel T7i", - "exposure_time": "1/125", - "aperture": "11.0", - "focal_length": "29.0", - "iso": 400 - } - }, - "hash": "80f447731e9107ec1499c90870b131d27f31b78d5cd1d4cc44e21805da924176", - "fileDate": "2023-02-07T21:57:22Z", - "photoDate": "2023-02-07T21:57:22Z", - "sortDate": "2023-02-07T21:57:22Z" -} \ No newline at end of file diff --git a/fixtures/1-asset/files/users/6610f22eb8093839698e1ba4 b/fixtures/1-asset/files/users/6610f22eb8093839698e1ba4 deleted file mode 100644 index 31545f1..0000000 --- a/fixtures/1-asset/files/users/6610f22eb8093839698e1ba4 +++ /dev/null @@ -1,7 +0,0 @@ -{ - "collections": { - "upload": "test-collection", - "default": "test-collection", - "access": [ "test-collection" ] - } -} \ No newline at end of file diff --git a/fixtures/1-asset/files/users/test-user b/fixtures/1-asset/files/users/test-user deleted file mode 100644 index 31545f1..0000000 --- a/fixtures/1-asset/files/users/test-user +++ /dev/null @@ -1,7 +0,0 @@ -{ - "collections": { - "upload": "test-collection", - "default": "test-collection", - "access": [ "test-collection" ] - } -} \ No newline at end of file diff --git a/fixtures/1-asset/metadata.js b/fixtures/1-asset/metadata.js new file mode 100644 index 0000000..4605c9e --- /dev/null +++ b/fixtures/1-asset/metadata.js @@ -0,0 +1,30 @@ +const metadata = [ + { + "_id": "63e9c63a9164637613e9ef4d", + "origFileName": "7kpC3MZ5BOg.jpg", + "contentType": "image/jpeg", + "width": 5954, + "height": 3590, + "description": "snow covered mountain trees", + "location": "Comox Valley, BC, Canada", + "searchText": " snow covered mountain trees Comox Valley, BC, Canada", + "properties": { + "exif": { + "make": "Canon", + "model": " EOS Rebel T7i", + "name": "Canon, EOS Rebel T7i", + "exposure_time": "1/125", + "aperture": "11.0", + "focal_length": "29.0", + "iso": 400 + } + }, + "hash": "80f447731e9107ec1499c90870b131d27f31b78d5cd1d4cc44e21805da924176", + "fileDate": new Date("2023-02-07T21:57:22Z"), + "photoDate": new Date("2023-02-07T21:57:22Z"), + "sortDate": new Date("2023-02-07T21:57:22Z"), + "setId": "test-collection", + } +]; + +module.exports = metadata; \ No newline at end of file diff --git a/fixtures/1-asset/sets.js b/fixtures/1-asset/sets.js new file mode 100644 index 0000000..297fdef --- /dev/null +++ b/fixtures/1-asset/sets.js @@ -0,0 +1,11 @@ +const sets = [ + { + "_id": "test-collection", + "name": "Test collection", + "owners": [ + "test-user" + ] + } +] + +module.exports = sets; \ No newline at end of file diff --git a/fixtures/1-asset/users.js b/fixtures/1-asset/users.js new file mode 100644 index 0000000..83bd6e6 --- /dev/null +++ b/fixtures/1-asset/users.js @@ -0,0 +1,12 @@ +const users = [ + { + "_id": "test-user", + "sets": { + "upload": "test-collection", + "default": "test-collection", + "access": [ "test-collection" ] + } + } +] + +module.exports = users; \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/00 b/fixtures/50-assets/files/collections/test-collection/journal/00 deleted file mode 100644 index 05d23d3..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/00 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef32", - "op": { - "type": "set", - "fields": { - "origFileName": "kBis_RVfGQM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5349, - "height": 8019, - "description": "a sandy beach with a body of water in the distance", - "location": "Ouddorp, Niederlande", - "searchText": " a sandy beach with a body of water in the distance Ouddorp, Niederlande", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/1000", - "aperture": "2.8", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "25c6d5bd92f9ef375e50b80196030e2903e4b9be896dce8a2fdfe7a8a3c1f365", - "fileDate": "2023-01-09T04:36:22Z", - "photoDate": "2023-01-09T04:36:22Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/01 b/fixtures/50-assets/files/collections/test-collection/journal/01 deleted file mode 100644 index 3ac8e79..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/01 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef33", - "op": { - "type": "set", - "fields": { - "origFileName": "JOvLQVXf9uA.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "Portland Fashion Model wearing a gold crown and necklace Portrait taken by Portland Photographer Lance Reis on my Sonya7iii in my photography studio.", - "location": "Portland, OR, USA", - "searchText": " Portland Fashion Model wearing a gold crown and necklace Portrait taken by Portland Photographer Lance Reis on my Sonya7iii in my photography studio. Portland, OR, USA", - "properties": { - "exif": { - "make": "Sony", - "model": null, - "name": "Sony", - "exposure_time": null, - "aperture": null, - "focal_length": "0.0", - "iso": null - } - }, - "hash": "39f8c3599cfee13335143cbd2e7893ef5fbd1b0454aaa6e7f51c47eadc9366fa", - "fileDate": "2023-01-09T15:50:46Z", - "photoDate": "2023-01-09T15:50:46Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/02 b/fixtures/50-assets/files/collections/test-collection/journal/02 deleted file mode 100644 index 064fe85..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/02 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef34", - "op": { - "type": "set", - "fields": { - "origFileName": "p9ttlbi5wig.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3600, - "height": 2400, - "description": "Smiling Catholic priest (jesuit) giving his blessing at New Yearˇs", - "location": "Trnava, Slovakia", - "searchText": " Smiling Catholic priest (jesuit) giving his blessing at New Yearˇs Trnava, Slovakia", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R6", - "name": "Canon, EOS R6", - "exposure_time": "1/400", - "aperture": "2", - "focal_length": "35.0", - "iso": 1600 - } - }, - "hash": "460b3f18faaed8f6841d44f3fd91916fec29b6fc68eaf3fb0cacd55b9f153499", - "fileDate": "2023-01-12T19:26:04Z", - "photoDate": "2023-01-12T19:26:04Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/03 b/fixtures/50-assets/files/collections/test-collection/journal/03 deleted file mode 100644 index f3f6f94..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/03 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef35", - "op": { - "type": "set", - "fields": { - "origFileName": "rOuTz5d10k8.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4923, - "height": 6154, - "description": "a woman with her hands on her hips", - "location": null, - "searchText": " a woman with her hands on her hips", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-6300", - "name": "SONY, ILCE-6300", - "exposure_time": "1/125", - "aperture": "2.8", - "focal_length": "75.0", - "iso": 800 - } - }, - "hash": "c3638f7d122c33f10c42f6ed792c7954018f050a35b13d1a2ad123f0fee783b6", - "fileDate": "2023-01-13T15:21:25Z", - "photoDate": "2023-01-13T15:21:25Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/04 b/fixtures/50-assets/files/collections/test-collection/journal/04 deleted file mode 100644 index 4c24ee4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/04 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef36", - "op": { - "type": "set", - "fields": { - "origFileName": "jZ5WZaD0v30.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5472, - "height": 3648, - "description": "Earthworks Audio - ETHOS - XLR Microphone used for podcasting, gaming, and chatting with friends.", - "location": null, - "searchText": " Earthworks Audio - ETHOS - XLR Microphone used for podcasting, gaming, and chatting with friends.", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R6", - "name": "Canon, EOS R6", - "exposure_time": "1/30", - "aperture": "3.2", - "focal_length": "35.0", - "iso": 1000 - } - }, - "hash": "419726ceacd326c0016991a00f9df1e712cf4798f77357ee5599b2be0d64a3f6", - "fileDate": "2023-01-13T22:34:00Z", - "photoDate": "2023-01-13T22:34:00Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/05 b/fixtures/50-assets/files/collections/test-collection/journal/05 deleted file mode 100644 index e49b365..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/05 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef37", - "op": { - "type": "set", - "fields": { - "origFileName": "To_ZxorMluI.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3667, - "height": 5500, - "description": "a vase with a flower and two eggs on a napkin", - "location": null, - "searchText": " a vase with a flower and two eggs on a napkin", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON D850", - "name": "NIKON CORPORATION, NIKON D850", - "exposure_time": "1/160", - "aperture": "14.0", - "focal_length": "90.0", - "iso": 160 - } - }, - "hash": "3ccb5904127ed37f291b5979a909366f062f1f97b03d7c5005d0a0c29c9f84d9", - "fileDate": "2023-01-16T20:21:03Z", - "photoDate": "2023-01-16T20:21:03Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/06 b/fixtures/50-assets/files/collections/test-collection/journal/06 deleted file mode 100644 index 70d5eb4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/06 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef38", - "op": { - "type": "set", - "fields": { - "origFileName": "Drh5FTN_tvU.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4024, - "height": 6048, - "description": "a person walking down a snow covered road", - "location": "Calgary, AB, Canada", - "searchText": " a person walking down a snow covered road Calgary, AB, Canada", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 6_2", - "name": "NIKON CORPORATION, NIKON Z 6_2", - "exposure_time": "1/500", - "aperture": "2.8", - "focal_length": "40.0", - "iso": 100 - } - }, - "hash": "d09dc11087b1fb93881ba61c75831e29c39d74191e505e95c82d63a922a3ea87", - "fileDate": "2023-01-16T21:35:04Z", - "photoDate": "2023-01-16T21:35:04Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/07 b/fixtures/50-assets/files/collections/test-collection/journal/07 deleted file mode 100644 index 71f870f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/07 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6379164637613e9ef39", - "op": { - "type": "set", - "fields": { - "origFileName": "syEwWS2ueXw.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5000, - "height": 7500, - "description": "a tray of cookies with the word love spelled out", - "location": null, - "searchText": " a tray of cookies with the word love spelled out", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 7_2", - "name": "NIKON CORPORATION, NIKON Z 7_2", - "exposure_time": "1/500", - "aperture": "1.8", - "focal_length": "50.0", - "iso": 160 - } - }, - "hash": "abb523db3ba1b12c7624b8a5acc341beaa8d0df028d0301f7aacfde528e1c220", - "fileDate": "2023-01-17T16:33:15Z", - "photoDate": "2023-01-17T16:33:15Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/08 b/fixtures/50-assets/files/collections/test-collection/journal/08 deleted file mode 100644 index 13958fb..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/08 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef3a", - "op": { - "type": "set", - "fields": { - "origFileName": "hKdlHLDZB_c.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5760, - "height": 3840, - "description": "a man in a hat is looking at the skyline", - "location": "London, London, United Kingdom", - "searchText": " a man in a hat is looking at the skyline London, London, United Kingdom", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS 5D Mark III", - "name": "Canon, EOS 5D Mark III", - "exposure_time": "1/250", - "aperture": "8", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "504345bccaa98396af0535e84947ece609ebd361efae26e7cffd35c12fdb2a12", - "fileDate": "2023-01-19T16:52:05Z", - "photoDate": "2023-01-19T16:52:05Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/09 b/fixtures/50-assets/files/collections/test-collection/journal/09 deleted file mode 100644 index f6a468e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/09 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef3b", - "op": { - "type": "set", - "fields": { - "origFileName": "oQ8QAqsWryc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4578, - "height": 6867, - "description": "Hazy Winter Day", - "location": "Pikkukosken uimaranta, Helsinki, Finland", - "searchText": " Hazy Winter Day Pikkukosken uimaranta, Helsinki, Finland", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M4", - "name": "SONY, ILCE-7M4", - "exposure_time": "1/125", - "aperture": "13.0", - "focal_length": "20.0", - "iso": 200 - } - }, - "hash": "5e1cbd39f832e0d67ad4833a91a376e028a560cdc3946b987a987ba8afc7463d", - "fileDate": "2023-01-22T21:35:05Z", - "photoDate": "2023-01-22T21:35:05Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/10 b/fixtures/50-assets/files/collections/test-collection/journal/10 deleted file mode 100644 index 80039fe..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/10 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef3c", - "op": { - "type": "set", - "fields": { - "origFileName": "NEmPI4C1D44.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3374, - "height": 4218, - "description": "A walk through Middle-Earth", - "location": "De Djawatan Forest, Purwosari, Benculuk, Banyuwangi Regency, East Java, Indonesia", - "searchText": " A walk through Middle-Earth De Djawatan Forest, Purwosari, Benculuk, Banyuwangi Regency, East Java, Indonesia", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/3200", - "aperture": "1.6", - "focal_length": "35.0", - "iso": 400 - } - }, - "hash": "7f15396e65408570f54d266dd465e465b8329949991a1bbeaa4f98e6593f8441", - "fileDate": "2023-01-23T06:06:50Z", - "photoDate": "2023-01-23T06:06:50Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/11 b/fixtures/50-assets/files/collections/test-collection/journal/11 deleted file mode 100644 index 8166e08..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/11 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef3d", - "op": { - "type": "set", - "fields": { - "origFileName": "LFq-HxcWT5E.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4061, - "height": 6091, - "description": "Experimental portrait", - "location": null, - "searchText": " Experimental portrait", - "properties": { - "exif": { - "make": "FUJIFILM", - "model": "X-T3", - "name": "FUJIFILM, X-T3", - "exposure_time": "1/60", - "aperture": "1.0", - "focal_length": "35.0", - "iso": 6400 - } - }, - "hash": "20508a8e387bc6fbc59b597e1affe289b2fda461e3d472af2c83979de58b6815", - "fileDate": "2023-01-23T21:06:37Z", - "photoDate": "2023-01-23T21:06:37Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/12 b/fixtures/50-assets/files/collections/test-collection/journal/12 deleted file mode 100644 index 3a2b6bb..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/12 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef3e", - "op": { - "type": "set", - "fields": { - "origFileName": "XpIsQWpxXps.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4236, - "height": 2829, - "description": "a person standing on a balcony of a building", - "location": null, - "searchText": " a person standing on a balcony of a building", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7SM3", - "name": "SONY, ILCE-7SM3", - "exposure_time": "1/60", - "aperture": "5.0", - "focal_length": "85.0", - "iso": 800 - } - }, - "hash": "a7db9b6e45bf43114c6ea9a38a9c0fbc44368ab96aee5687e44c9fe9a3cc8e1f", - "fileDate": "2023-01-25T02:36:35Z", - "photoDate": "2023-01-25T02:36:35Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/13 b/fixtures/50-assets/files/collections/test-collection/journal/13 deleted file mode 100644 index 62f9d2c..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/13 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef3f", - "op": { - "type": "set", - "fields": { - "origFileName": "mx3RynEbCV4.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5464, - "height": 3640, - "description": "a winding road in the middle of a snow covered forest", - "location": "Weinheim, Deutschland", - "searchText": " a winding road in the middle of a snow covered forest Weinheim, Deutschland", - "properties": { - "exif": { - "make": "DJI", - "model": "FC3411", - "name": "DJI, FC3411", - "exposure_time": "1/640", - "aperture": "2.8", - "focal_length": "8.4", - "iso": 100 - } - }, - "hash": "07e825f74bd7841abd03e1eda0ffcdb045abd19fcb3ebf2661daec1afc94b85b", - "fileDate": "2023-01-27T22:48:28Z", - "photoDate": "2023-01-27T22:48:28Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/14 b/fixtures/50-assets/files/collections/test-collection/journal/14 deleted file mode 100644 index 9e8201c..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/14 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef40", - "op": { - "type": "set", - "fields": { - "origFileName": "AecCj5qi88M.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2624, - "height": 3936, - "description": "a man sitting at a desk working on a laptop", - "location": null, - "searchText": " a man sitting at a desk working on a laptop", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7C", - "name": "SONY, ILCE-7C", - "exposure_time": "1/80", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 100 - } - }, - "hash": "e664fd9bd8f9e4d67b7647959134a5152277e4284bb615cbb6d987cc68aeafa4", - "fileDate": "2023-01-28T18:33:13Z", - "photoDate": "2023-01-28T18:33:13Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/15 b/fixtures/50-assets/files/collections/test-collection/journal/15 deleted file mode 100644 index 0390a37..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/15 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef41", - "op": { - "type": "set", - "fields": { - "origFileName": "TJ3Xl4YgHVs.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4928, - "height": 3264, - "description": "a large body of water surrounded by mountains", - "location": "Norway", - "searchText": " a large body of water surrounded by mountains Norway", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "17228a9a2757e30fbb12a23f2cdc564758dd509c7d947173c4b7d018f9a750cb", - "fileDate": "2023-01-28T19:37:15Z", - "photoDate": "2023-01-28T19:37:15Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/16 b/fixtures/50-assets/files/collections/test-collection/journal/16 deleted file mode 100644 index 616620a..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/16 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef42", - "op": { - "type": "set", - "fields": { - "origFileName": "SMSpk9fprcU.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 7189, - "height": 4795, - "description": "a woman sitting on a bench in a gym", - "location": null, - "searchText": " a woman sitting on a bench in a gym", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/125", - "aperture": "1.8", - "focal_length": "35.0", - "iso": 320 - } - }, - "hash": "45409a02deead8e49ae06d47eb9d09b49005b688d6016459f358a504ab1b0d87", - "fileDate": "2023-01-29T21:19:53Z", - "photoDate": "2023-01-29T21:19:53Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/17 b/fixtures/50-assets/files/collections/test-collection/journal/17 deleted file mode 100644 index 252ac2e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/17 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6389164637613e9ef43", - "op": { - "type": "set", - "fields": { - "origFileName": "THI4cJ-HGvw.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5833, - "height": 3889, - "description": "a woman riding a horse through the ocean", - "location": "Kwinana, WA, Australia", - "searchText": " a woman riding a horse through the ocean Kwinana, WA, Australia", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/8000", - "aperture": "2.0", - "focal_length": "85.0", - "iso": 50 - } - }, - "hash": "d905df09a4709066722677eff7815a5bbb7939e2406367043f85b7920c2ab544", - "fileDate": "2023-01-30T06:27:38Z", - "photoDate": "2023-01-30T06:27:38Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/18 b/fixtures/50-assets/files/collections/test-collection/journal/18 deleted file mode 100644 index 6fd099a..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/18 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef44", - "op": { - "type": "set", - "fields": { - "origFileName": "sW8AATej1Xc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "a man kneeling down on top of a green holding a golf club", - "location": null, - "searchText": " a man kneeling down on top of a green holding a golf club", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "ba21e6dadd970312facdd2ce412c311151476de2eedcc1dcc3a6c6f0ae415934", - "fileDate": "2023-01-30T19:46:02Z", - "photoDate": "2023-01-30T19:46:02Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/19 b/fixtures/50-assets/files/collections/test-collection/journal/19 deleted file mode 100644 index 10e2f6a..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/19 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef45", - "op": { - "type": "set", - "fields": { - "origFileName": "b2873V_RcEw.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3740, - "height": 4675, - "description": "a brown cow standing in a snow covered field", - "location": "Puy-de-Dôme, France", - "searchText": " a brown cow standing in a snow covered field Puy-de-Dôme, France", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "c30c32312c81396af09ab7d49b230d51ee97124b6f2532a2a556db36d71a501b", - "fileDate": "2023-01-31T17:24:52Z", - "photoDate": "2023-01-31T17:24:52Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/20 b/fixtures/50-assets/files/collections/test-collection/journal/20 deleted file mode 100644 index 93951c0..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/20 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef46", - "op": { - "type": "set", - "fields": { - "origFileName": "fO7tnJ3t5dY.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3093, - "height": 4640, - "description": "Standing on Hopkins Landing dock in Gibsons, B.C", - "location": "Gibsons, BC, Canada", - "searchText": " Standing on Hopkins Landing dock in Gibsons, B.C Gibsons, BC, Canada", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R7", - "name": "Canon, EOS R7", - "exposure_time": "1/50", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 250 - } - }, - "hash": "681d21f0d674a54acf2881f51f4ee6f721a2a9b288ba0f3e261e16a2d48c8fac", - "fileDate": "2023-01-31T20:27:28Z", - "photoDate": "2023-01-31T20:27:28Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/21 b/fixtures/50-assets/files/collections/test-collection/journal/21 deleted file mode 100644 index 9e0c601..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/21 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef47", - "op": { - "type": "set", - "fields": { - "origFileName": "Wlvi9rtFOm8.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4419, - "height": 6628, - "description": "a fountain in the middle of a courtyard", - "location": null, - "searchText": " a fountain in the middle of a courtyard", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M4", - "name": "SONY, ILCE-7M4", - "exposure_time": "1/400", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 125 - } - }, - "hash": "64743a92a61a587730ceb444ccfcb535126315e993ae2932224a0716aa544c09", - "fileDate": "2023-02-01T17:35:29Z", - "photoDate": "2023-02-01T17:35:29Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/22 b/fixtures/50-assets/files/collections/test-collection/journal/22 deleted file mode 100644 index 3fb5aa4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/22 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef48", - "op": { - "type": "set", - "fields": { - "origFileName": "ZTXnyniUaLY.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6720, - "height": 4480, - "description": "a man with a beard wearing a hoodie", - "location": "Flint, Flint, United States", - "searchText": " a man with a beard wearing a hoodie Flint, Flint, United States", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R", - "name": "Canon, EOS R", - "exposure_time": "1/100", - "aperture": "3.2", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "e6752c9bee24f7427dd85bfc45f0ec9582c951fb37ebfaef9e01950ad1a22a94", - "fileDate": "2023-02-02T07:09:20Z", - "photoDate": "2023-02-02T07:09:20Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/23 b/fixtures/50-assets/files/collections/test-collection/journal/23 deleted file mode 100644 index 2729670..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/23 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef49", - "op": { - "type": "set", - "fields": { - "origFileName": "16i3KFIlR7I.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6890, - "height": 9504, - "description": "mobina", - "location": null, - "searchText": " mobina", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7RM4", - "name": "SONY, ILCE-7RM4", - "exposure_time": "1/80", - "aperture": "3.5", - "focal_length": "35.0", - "iso": 250 - } - }, - "hash": "65148c4d1710631331e37a4bff52311bc449d68d5186e7cfbf9e94ffc8b593af", - "fileDate": "2023-02-02T16:56:18Z", - "photoDate": "2023-02-02T16:56:18Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/24 b/fixtures/50-assets/files/collections/test-collection/journal/24 deleted file mode 100644 index eef0ec3..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/24 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef4a", - "op": { - "type": "set", - "fields": { - "origFileName": "Hy37gkO-AYs.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6000, - "height": 4000, - "description": "a snowboarder is going down a snowy mountain", - "location": "Peyragudes, Loudervielle, France", - "searchText": " a snowboarder is going down a snowy mountain Peyragudes, Loudervielle, France", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7C", - "name": "SONY, ILCE-7C", - "exposure_time": "1/2000", - "aperture": "8.0", - "focal_length": "75.0", - "iso": 125 - } - }, - "hash": "3de8a1e8307af46081027e5e8c6714c191b6bdbd522b1a005652a6f29f1b2fb5", - "fileDate": "2023-02-02T19:45:52Z", - "photoDate": "2023-02-02T19:45:52Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/25 b/fixtures/50-assets/files/collections/test-collection/journal/25 deleted file mode 100644 index dd8d850..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/25 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef4b", - "op": { - "type": "set", - "fields": { - "origFileName": "Ie-uzpPyoro.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "a train traveling down train tracks next to a tunnel", - "location": null, - "searchText": " a train traveling down train tracks next to a tunnel", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/20", - "aperture": "2.8", - "focal_length": "43.2", - "iso": 320 - } - }, - "hash": "9de67bcbd8c2a996081461f66ecc766a494809cd29367d6a169f4a91519044e8", - "fileDate": "2023-02-03T00:26:56Z", - "photoDate": "2023-02-03T00:26:56Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/26 b/fixtures/50-assets/files/collections/test-collection/journal/26 deleted file mode 100644 index 75d65a4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/26 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c6399164637613e9ef4c", - "op": { - "type": "set", - "fields": { - "origFileName": "x6-w01edyk0.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5472, - "height": 3648, - "description": "an aerial view of a beach with rocks and water", - "location": null, - "searchText": " an aerial view of a beach with rocks and water", - "properties": { - "exif": { - "make": "Hasselblad", - "model": "L1D-20c", - "name": "Hasselblad, L1D-20c", - "exposure_time": "1/240", - "aperture": "3.5", - "focal_length": "10.3", - "iso": 100 - } - }, - "hash": "69ca616bccac1c64df31c29278f59fbf55ec09c4dafc4ed8ec1d85371fff73b0", - "fileDate": "2023-02-05T09:22:19Z", - "photoDate": "2023-02-05T09:22:19Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/27 b/fixtures/50-assets/files/collections/test-collection/journal/27 deleted file mode 100644 index dc0fef8..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/27 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef4d", - "op": { - "type": "set", - "fields": { - "origFileName": "7kpC3MZ5BOg.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5954, - "height": 3590, - "description": "snow covered mountain trees", - "location": "Comox Valley, BC, Canada", - "searchText": " snow covered mountain trees Comox Valley, BC, Canada", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS Rebel T7i", - "name": "Canon, EOS Rebel T7i", - "exposure_time": "1/125", - "aperture": "11.0", - "focal_length": "29.0", - "iso": 400 - } - }, - "hash": "80f447731e9107ec1499c90870b131d27f31b78d5cd1d4cc44e21805da924176", - "fileDate": "2023-02-07T21:57:22Z", - "photoDate": "2023-02-07T21:57:22Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/28 b/fixtures/50-assets/files/collections/test-collection/journal/28 deleted file mode 100644 index 4299dca..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/28 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef4e", - "op": { - "type": "set", - "fields": { - "origFileName": "NK9qJ9LRyPc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6240, - "height": 4160, - "description": "a plant in a pot sitting on a table next to a stair case", - "location": null, - "searchText": " a plant in a pot sitting on a table next to a stair case", - "properties": { - "exif": { - "make": "FUJIFILM", - "model": "X100V", - "name": "FUJIFILM, X100V", - "exposure_time": "1/600", - "aperture": "2.2", - "focal_length": "23.0", - "iso": 640 - } - }, - "hash": "0664a7ce86769e3754495f8af1205067d6d5f15715c4cf57f795f85cf6a6dd83", - "fileDate": "2023-02-09T14:49:56Z", - "photoDate": "2023-02-09T14:49:56Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/29 b/fixtures/50-assets/files/collections/test-collection/journal/29 deleted file mode 100644 index e1d0cb9..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/29 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef4f", - "op": { - "type": "set", - "fields": { - "origFileName": "KSXGw3K3KY0.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 7262, - "height": 4844, - "description": "a close up of a toilet seat with the lid down", - "location": null, - "searchText": " a close up of a toilet seat with the lid down", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/800", - "aperture": null, - "focal_length": null, - "iso": 100 - } - }, - "hash": "ab21d7ac3be13b98855ac39db5e9c9e46c97e848fef2c1876953d8ff586b455e", - "fileDate": "2023-02-10T17:25:56Z", - "photoDate": "2023-02-10T17:25:56Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/30 b/fixtures/50-assets/files/collections/test-collection/journal/30 deleted file mode 100644 index fb783e5..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/30 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef50", - "op": { - "type": "set", - "fields": { - "origFileName": "-qDgiE5Vyww.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3200, - "height": 2400, - "description": "a living room with a book shelf and a plant", - "location": "Akure, Nigeria", - "searchText": " a living room with a book shelf and a plant Akure, Nigeria", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "c7785d7fa96e932e97bc9c41bc0cf3f017daf77899ae421d848e1a2d9557f15c", - "fileDate": "2023-01-12T22:54:12Z", - "photoDate": "2023-01-12T22:54:12Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/31 b/fixtures/50-assets/files/collections/test-collection/journal/31 deleted file mode 100644 index 019de11..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/31 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef51", - "op": { - "type": "set", - "fields": { - "origFileName": "L0Bte153mM8.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2955, - "height": 2364, - "description": "a herd of horses standing next to each other", - "location": null, - "searchText": " a herd of horses standing next to each other", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "c5c8c7a95c777994e1c7dec0b830e8fc29a6ee25881bb10492897727e02d02bf", - "fileDate": "2023-01-13T14:11:04Z", - "photoDate": "2023-01-13T14:11:04Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/32 b/fixtures/50-assets/files/collections/test-collection/journal/32 deleted file mode 100644 index 3852bdc..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/32 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63a9164637613e9ef52", - "op": { - "type": "set", - "fields": { - "origFileName": "w3Zc6MHmpmY.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 8256, - "height": 5504, - "description": "a sandy beach with grass growing on top of it", - "location": null, - "searchText": " a sandy beach with grass growing on top of it", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON D850", - "name": "NIKON CORPORATION, NIKON D850", - "exposure_time": "1/2000", - "aperture": "6.7", - "focal_length": "50.0", - "iso": 800 - } - }, - "hash": "380179fdd2d496276ae39cd85246045ab73c0e817ee50017fb9afe169c3e92ab", - "fileDate": "2023-01-14T01:32:34Z", - "photoDate": "2023-01-14T01:32:34Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/33 b/fixtures/50-assets/files/collections/test-collection/journal/33 deleted file mode 100644 index 8c36472..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/33 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef53", - "op": { - "type": "set", - "fields": { - "origFileName": "xCSP0fMNDkE.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4990, - "height": 6653, - "description": "This lonely road is just beautiful", - "location": "Cabrera, 33000, Dominican Republic", - "searchText": " This lonely road is just beautiful Cabrera, 33000, Dominican Republic", - "properties": { - "exif": { - "make": "DJI", - "model": "Mini Pro 3", - "name": "DJI, Mini Pro 3", - "exposure_time": "1/6", - "aperture": "1.7", - "focal_length": "6.7", - "iso": 200 - } - }, - "hash": "9ca378e5cf846c4b78a3cf6eb515558eb62fd8ca88cc2800aa588a0903c464ee", - "fileDate": "2023-01-14T01:48:52Z", - "photoDate": "2023-01-14T01:48:52Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/34 b/fixtures/50-assets/files/collections/test-collection/journal/34 deleted file mode 100644 index 1015454..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/34 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef54", - "op": { - "type": "set", - "fields": { - "origFileName": "a5C4QyqXH7I.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2555, - "height": 3832, - "description": "a man standing in front of a body of water", - "location": null, - "searchText": " a man standing in front of a body of water", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS 6D", - "name": "Canon, EOS 6D", - "exposure_time": "1/200", - "aperture": "2.2", - "focal_length": "85.0", - "iso": 100 - } - }, - "hash": "477f1dbde89658e34813fff1107b5b1dec077f0ef5dd1d56b7e5ef14439932c4", - "fileDate": "2023-01-17T16:20:35Z", - "photoDate": "2023-01-17T16:20:35Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/35 b/fixtures/50-assets/files/collections/test-collection/journal/35 deleted file mode 100644 index 7906538..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/35 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef55", - "op": { - "type": "set", - "fields": { - "origFileName": "WWEFneQ5XAk.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5464, - "height": 8192, - "description": "journaling bible open to psalm 63 with notes", - "location": null, - "searchText": " journaling bible open to psalm 63 with notes", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/1250", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 800 - } - }, - "hash": "c70b2a0ebd7b97ba256b0e82677a28afd647fb0520c148cd51193f60a927568e", - "fileDate": "2023-01-17T18:25:14Z", - "photoDate": "2023-01-17T18:25:14Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/36 b/fixtures/50-assets/files/collections/test-collection/journal/36 deleted file mode 100644 index 74a6b32..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/36 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef56", - "op": { - "type": "set", - "fields": { - "origFileName": "kp-wF-ZCHRo.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3343, - "height": 4179, - "description": "a building with a clock on the side of it", - "location": "Avilés, España", - "searchText": " a building with a clock on the side of it Avilés, España", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON D7500", - "name": "NIKON CORPORATION, NIKON D7500", - "exposure_time": "1/1000", - "aperture": "4.5", - "focal_length": "35.0", - "iso": 100 - } - }, - "hash": "b7b4fa54dbda2c3218abb48ea4af8620ece637a4af9d08b92dd5850ea3b76696", - "fileDate": "2023-01-18T16:59:18Z", - "photoDate": "2023-01-18T16:59:18Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/37 b/fixtures/50-assets/files/collections/test-collection/journal/37 deleted file mode 100644 index ee752f9..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/37 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef57", - "op": { - "type": "set", - "fields": { - "origFileName": "fZL3vx4pLuM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "Paros, Greece.", - "location": "Paros, Greece", - "searchText": " Paros, Greece. Paros, Greece", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "0766b644a3f7b5afa8e260a630e2d639db273ecd24fbcfc7f8211930a44405c0", - "fileDate": "2023-01-23T08:21:41Z", - "photoDate": "2023-01-23T08:21:41Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/38 b/fixtures/50-assets/files/collections/test-collection/journal/38 deleted file mode 100644 index 41e955e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/38 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef58", - "op": { - "type": "set", - "fields": { - "origFileName": "-do1gkYi21c.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6016, - "description": "🌷", - "location": "Hanover, Hanover, Jamaica", - "searchText": " 🌷 Hanover, Hanover, Jamaica", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "dc5d065ee0d2c8eb01ae14b6c2b82014b810beaec4d8bd1c0bc3aa48f702696e", - "fileDate": "2023-01-23T18:54:30Z", - "photoDate": "2023-01-23T18:54:30Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/39 b/fixtures/50-assets/files/collections/test-collection/journal/39 deleted file mode 100644 index 0dda049..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/39 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef59", - "op": { - "type": "set", - "fields": { - "origFileName": "v4x6y2q92LI.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3600, - "height": 5408, - "description": "a building with a clock tower in the middle of a city", - "location": "Denver, CO, USA", - "searchText": " a building with a clock tower in the middle of a city Denver, CO, USA", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 7_2", - "name": "NIKON CORPORATION, NIKON Z 7_2", - "exposure_time": "1/1000", - "aperture": "1.4", - "focal_length": "35.0", - "iso": 125 - } - }, - "hash": "63e0696d186d1851b739552b1d4e1e93b9c3cb5a7843e73793ac48da046befe0", - "fileDate": "2023-01-23T18:59:57Z", - "photoDate": "2023-01-23T18:59:57Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/40 b/fixtures/50-assets/files/collections/test-collection/journal/40 deleted file mode 100644 index 79d6d79..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/40 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef5a", - "op": { - "type": "set", - "fields": { - "origFileName": "bwOklw2MKXc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3961, - "height": 5942, - "description": "Experimental portrait", - "location": null, - "searchText": " Experimental portrait", - "properties": { - "exif": { - "make": "FUJIFILM", - "model": "X-T3", - "name": "FUJIFILM, X-T3", - "exposure_time": "1/280", - "aperture": "1.4", - "focal_length": "35.0", - "iso": 6400 - } - }, - "hash": "553520d9686d63c49ca180fc9169f4a5d7329d5cb4c8b2be4ef205e1ea5797a8", - "fileDate": "2023-01-23T21:06:37Z", - "photoDate": "2023-01-23T21:06:37Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/41 b/fixtures/50-assets/files/collections/test-collection/journal/41 deleted file mode 100644 index 20b04b9..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/41 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63b9164637613e9ef5b", - "op": { - "type": "set", - "fields": { - "origFileName": "Xby93pk7tOM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4024, - "height": 6048, - "description": "a man with long hair and a beard with tattoos", - "location": null, - "searchText": " a man with long hair and a beard with tattoos", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 6", - "name": "NIKON CORPORATION, NIKON Z 6", - "exposure_time": "1/800", - "aperture": "1.4", - "focal_length": "24.0", - "iso": 80 - } - }, - "hash": "f585fe0383fcbfb208b621c4a9dce0835545c465c637c7940cf5a06c843ed1cd", - "fileDate": "2023-01-25T01:09:13Z", - "photoDate": "2023-01-25T01:09:13Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/42 b/fixtures/50-assets/files/collections/test-collection/journal/42 deleted file mode 100644 index 9433044..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/42 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef5c", - "op": { - "type": "set", - "fields": { - "origFileName": "UHWLE1rrBng.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3555, - "height": 5244, - "description": "ig - @pilseeta", - "location": "Mārupe, Mārupes novads, Latvia", - "searchText": " ig - @pilseeta Mārupe, Mārupes novads, Latvia", - "properties": { - "exif": { - "make": "Premier", - "model": "AF M-8000", - "name": "Premier, AF M-8000", - "exposure_time": null, - "aperture": null, - "focal_length": "0.0", - "iso": null - } - }, - "hash": "290cc76da5c4528711e36be80c6db2ae341cecd9a50c0f13e1c38614cf834f58", - "fileDate": "2023-01-25T11:09:29Z", - "photoDate": "2023-01-25T11:09:29Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/43 b/fixtures/50-assets/files/collections/test-collection/journal/43 deleted file mode 100644 index a8772a5..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/43 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef5d", - "op": { - "type": "set", - "fields": { - "origFileName": "wfromvPVGhI.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4400, - "height": 6600, - "description": "a mountain covered in snow and trees under a cloudy sky", - "location": null, - "searchText": " a mountain covered in snow and trees under a cloudy sky", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7RM3", - "name": "SONY, ILCE-7RM3", - "exposure_time": "1/250", - "aperture": "5.6", - "focal_length": "70.0", - "iso": 1000 - } - }, - "hash": "51eb0f882a47f162b31e2c01f4ce6d58412557978b4fc825a402c26d2cef67d1", - "fileDate": "2023-01-25T19:01:09Z", - "photoDate": "2023-01-25T19:01:09Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/44 b/fixtures/50-assets/files/collections/test-collection/journal/44 deleted file mode 100644 index be4ba42..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/44 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef5e", - "op": { - "type": "set", - "fields": { - "origFileName": "YpUcxEnAy5k.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "a close up of a blue and green object", - "location": null, - "searchText": " a close up of a blue and green object", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS 2000D", - "name": "Canon, EOS 2000D", - "exposure_time": "1/25", - "aperture": "5.6", - "focal_length": "50.0", - "iso": 400 - } - }, - "hash": "305833625ad85adb6778c3bbb512b5a08fe577bf1536c1864b24307e25cd317f", - "fileDate": "2023-01-28T23:35:43Z", - "photoDate": "2023-01-28T23:35:43Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/45 b/fixtures/50-assets/files/collections/test-collection/journal/45 deleted file mode 100644 index 9d42aa4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/45 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef5f", - "op": { - "type": "set", - "fields": { - "origFileName": "Ohq7b4-gAmA.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3557, - "height": 5336, - "description": "a woman sitting on top of a black suitcase", - "location": "Hamburg, Germany", - "searchText": " a woman sitting on top of a black suitcase Hamburg, Germany", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/200", - "aperture": "2.5", - "focal_length": "85.0", - "iso": 400 - } - }, - "hash": "11d4ef27322a97821d683746421605385a3b7f2a257cb663a233125a1f4074eb", - "fileDate": "2023-01-29T04:12:02Z", - "photoDate": "2023-01-29T04:12:02Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/46 b/fixtures/50-assets/files/collections/test-collection/journal/46 deleted file mode 100644 index 7ac31b6..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/46 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef60", - "op": { - "type": "set", - "fields": { - "origFileName": "u5nuXl4_deQ.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 3000, - "description": "a blue pillow with a bunch of different objects on it", - "location": null, - "searchText": " a blue pillow with a bunch of different objects on it", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "7f76e8f047a9eaea2463e9a3f79429dcb5cdcaf40a16e156fd5020f3e2fdda7a", - "fileDate": "2023-01-29T21:52:42Z", - "photoDate": "2023-01-29T21:52:42Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/47 b/fixtures/50-assets/files/collections/test-collection/journal/47 deleted file mode 100644 index 75c1fd8..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/47 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef61", - "op": { - "type": "set", - "fields": { - "origFileName": "J-Nir7H1j0o.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4635, - "height": 5794, - "description": "a person sitting at a table with a plate of food", - "location": null, - "searchText": " a person sitting at a table with a plate of food", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/125", - "aperture": "4.0", - "focal_length": "35.0", - "iso": 640 - } - }, - "hash": "4374e98d33bcd9e6033738a597653e49d92d3d1fdffce75f4122b8c9728cc0f7", - "fileDate": "2023-01-30T05:16:59Z", - "photoDate": "2023-01-30T05:16:59Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/48 b/fixtures/50-assets/files/collections/test-collection/journal/48 deleted file mode 100644 index 9b531c4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/48 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef62", - "op": { - "type": "set", - "fields": { - "origFileName": "7oMcrpVYVwM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4695, - "height": 5869, - "description": "a woman sitting at a table with a plate of food", - "location": null, - "searchText": " a woman sitting at a table with a plate of food", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/125", - "aperture": "4.0", - "focal_length": "70.0", - "iso": 200 - } - }, - "hash": "25bc960e37a49183fc5dd8aef81aa53d603565e7bdbb3bf8b0d835c2db5a8ead", - "fileDate": "2023-01-30T05:16:59Z", - "photoDate": "2023-01-30T05:16:59Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/journal/49 b/fixtures/50-assets/files/collections/test-collection/journal/49 deleted file mode 100644 index 1ab2bcd..0000000 --- a/fixtures/50-assets/files/collections/test-collection/journal/49 +++ /dev/null @@ -1,32 +0,0 @@ -{ - "clientId": "some-client-id", - "id": "63e9c63c9164637613e9ef63", - "op": { - "type": "set", - "fields": { - "origFileName": "rrkau-m9Yec.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2141, - "height": 3211, - "description": "Sydney Tower Eye", - "location": "悉尼, 悉尼, 澳大利亚", - "searchText": " Sydney Tower Eye 悉尼, 悉尼, 澳大利亚", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R6", - "name": "Canon, EOS R6", - "exposure_time": "1/2500", - "aperture": "2.8", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "76a3438febf8a96a1fd0b1515017dc5ca3d7f3372ee69e84ce4de58cd3327501", - "fileDate": "2023-01-30T05:37:55Z", - "photoDate": "2023-01-30T05:37:55Z" - } - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata.json b/fixtures/50-assets/files/collections/test-collection/metadata.json deleted file mode 100644 index 7b91f2c..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Test collection", - "owners": [ - "test-user" - ] -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef32 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef32 deleted file mode 100644 index 40c408e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef32 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef32", - "origFileName": "kBis_RVfGQM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5349, - "height": 8019, - "description": "a sandy beach with a body of water in the distance", - "location": "Ouddorp, Niederlande", - "searchText": " a sandy beach with a body of water in the distance Ouddorp, Niederlande", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/1000", - "aperture": "2.8", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "25c6d5bd92f9ef375e50b80196030e2903e4b9be896dce8a2fdfe7a8a3c1f365", - "fileDate": "2023-01-09T04:36:22Z", - "photoDate": "2023-01-09T04:36:22Z", - "sortDate": "2023-01-09T04:36:22Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef33 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef33 deleted file mode 100644 index 78ae91b..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef33 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef33", - "origFileName": "JOvLQVXf9uA.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "Portland Fashion Model wearing a gold crown and necklace Portrait taken by Portland Photographer Lance Reis on my Sonya7iii in my photography studio.", - "location": "Portland, OR, USA", - "searchText": " Portland Fashion Model wearing a gold crown and necklace Portrait taken by Portland Photographer Lance Reis on my Sonya7iii in my photography studio. Portland, OR, USA", - "properties": { - "exif": { - "make": "Sony", - "model": null, - "name": "Sony", - "exposure_time": null, - "aperture": null, - "focal_length": "0.0", - "iso": null - } - }, - "hash": "39f8c3599cfee13335143cbd2e7893ef5fbd1b0454aaa6e7f51c47eadc9366fa", - "fileDate": "2023-01-09T15:50:46Z", - "photoDate": "2023-01-09T15:50:46Z", - "sortDate": "2023-01-09T15:50:46Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef34 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef34 deleted file mode 100644 index c65813c..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef34 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef34", - "origFileName": "p9ttlbi5wig.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3600, - "height": 2400, - "description": "Smiling Catholic priest (jesuit) giving his blessing at New Yearˇs", - "location": "Trnava, Slovakia", - "searchText": " Smiling Catholic priest (jesuit) giving his blessing at New Yearˇs Trnava, Slovakia", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R6", - "name": "Canon, EOS R6", - "exposure_time": "1/400", - "aperture": "2", - "focal_length": "35.0", - "iso": 1600 - } - }, - "hash": "460b3f18faaed8f6841d44f3fd91916fec29b6fc68eaf3fb0cacd55b9f153499", - "fileDate": "2023-01-12T19:26:04Z", - "photoDate": "2023-01-12T19:26:04Z", - "sortDate": "2023-01-12T19:26:04Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef35 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef35 deleted file mode 100644 index 8d16a2d..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef35 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef35", - "origFileName": "rOuTz5d10k8.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4923, - "height": 6154, - "description": "a woman with her hands on her hips", - "location": null, - "searchText": " a woman with her hands on her hips", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-6300", - "name": "SONY, ILCE-6300", - "exposure_time": "1/125", - "aperture": "2.8", - "focal_length": "75.0", - "iso": 800 - } - }, - "hash": "c3638f7d122c33f10c42f6ed792c7954018f050a35b13d1a2ad123f0fee783b6", - "fileDate": "2023-01-13T15:21:25Z", - "photoDate": "2023-01-13T15:21:25Z", - "sortDate": "2023-01-13T15:21:25Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef36 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef36 deleted file mode 100644 index b9fb363..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef36 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef36", - "origFileName": "jZ5WZaD0v30.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5472, - "height": 3648, - "description": "Earthworks Audio - ETHOS - XLR Microphone used for podcasting, gaming, and chatting with friends.", - "location": null, - "searchText": " Earthworks Audio - ETHOS - XLR Microphone used for podcasting, gaming, and chatting with friends.", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R6", - "name": "Canon, EOS R6", - "exposure_time": "1/30", - "aperture": "3.2", - "focal_length": "35.0", - "iso": 1000 - } - }, - "hash": "419726ceacd326c0016991a00f9df1e712cf4798f77357ee5599b2be0d64a3f6", - "fileDate": "2023-01-13T22:34:00Z", - "photoDate": "2023-01-13T22:34:00Z", - "sortDate": "2023-01-13T22:34:00Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef37 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef37 deleted file mode 100644 index 54a7a6f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef37 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef37", - "origFileName": "To_ZxorMluI.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3667, - "height": 5500, - "description": "a vase with a flower and two eggs on a napkin", - "location": null, - "searchText": " a vase with a flower and two eggs on a napkin", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON D850", - "name": "NIKON CORPORATION, NIKON D850", - "exposure_time": "1/160", - "aperture": "14.0", - "focal_length": "90.0", - "iso": 160 - } - }, - "hash": "3ccb5904127ed37f291b5979a909366f062f1f97b03d7c5005d0a0c29c9f84d9", - "fileDate": "2023-01-16T20:21:03Z", - "photoDate": "2023-01-16T20:21:03Z", - "sortDate": "2023-01-16T20:21:03Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef38 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef38 deleted file mode 100644 index 62d6bb2..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef38 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef38", - "origFileName": "Drh5FTN_tvU.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4024, - "height": 6048, - "description": "a person walking down a snow covered road", - "location": "Calgary, AB, Canada", - "searchText": " a person walking down a snow covered road Calgary, AB, Canada", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 6_2", - "name": "NIKON CORPORATION, NIKON Z 6_2", - "exposure_time": "1/500", - "aperture": "2.8", - "focal_length": "40.0", - "iso": 100 - } - }, - "hash": "d09dc11087b1fb93881ba61c75831e29c39d74191e505e95c82d63a922a3ea87", - "fileDate": "2023-01-16T21:35:04Z", - "photoDate": "2023-01-16T21:35:04Z", - "sortDate": "2023-01-16T21:35:04Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef39 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef39 deleted file mode 100644 index 646eb9b..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6379164637613e9ef39 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6379164637613e9ef39", - "origFileName": "syEwWS2ueXw.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5000, - "height": 7500, - "description": "a tray of cookies with the word love spelled out", - "location": null, - "searchText": " a tray of cookies with the word love spelled out", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 7_2", - "name": "NIKON CORPORATION, NIKON Z 7_2", - "exposure_time": "1/500", - "aperture": "1.8", - "focal_length": "50.0", - "iso": 160 - } - }, - "hash": "abb523db3ba1b12c7624b8a5acc341beaa8d0df028d0301f7aacfde528e1c220", - "fileDate": "2023-01-17T16:33:15Z", - "photoDate": "2023-01-17T16:33:15Z", - "sortDate": "2023-01-17T16:33:15Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3a b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3a deleted file mode 100644 index 20dccda..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3a +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef3a", - "origFileName": "hKdlHLDZB_c.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5760, - "height": 3840, - "description": "a man in a hat is looking at the skyline", - "location": "London, London, United Kingdom", - "searchText": " a man in a hat is looking at the skyline London, London, United Kingdom", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS 5D Mark III", - "name": "Canon, EOS 5D Mark III", - "exposure_time": "1/250", - "aperture": "8", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "504345bccaa98396af0535e84947ece609ebd361efae26e7cffd35c12fdb2a12", - "fileDate": "2023-01-19T16:52:05Z", - "photoDate": "2023-01-19T16:52:05Z", - "sortDate": "2023-01-19T16:52:05Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3b b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3b deleted file mode 100644 index c689443..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3b +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef3b", - "origFileName": "oQ8QAqsWryc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4578, - "height": 6867, - "description": "Hazy Winter Day", - "location": "Pikkukosken uimaranta, Helsinki, Finland", - "searchText": " Hazy Winter Day Pikkukosken uimaranta, Helsinki, Finland", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M4", - "name": "SONY, ILCE-7M4", - "exposure_time": "1/125", - "aperture": "13.0", - "focal_length": "20.0", - "iso": 200 - } - }, - "hash": "5e1cbd39f832e0d67ad4833a91a376e028a560cdc3946b987a987ba8afc7463d", - "fileDate": "2023-01-22T21:35:05Z", - "photoDate": "2023-01-22T21:35:05Z", - "sortDate": "2023-01-22T21:35:05Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3c b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3c deleted file mode 100644 index b15b34f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3c +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef3c", - "origFileName": "NEmPI4C1D44.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3374, - "height": 4218, - "description": "A walk through Middle-Earth", - "location": "De Djawatan Forest, Purwosari, Benculuk, Banyuwangi Regency, East Java, Indonesia", - "searchText": " A walk through Middle-Earth De Djawatan Forest, Purwosari, Benculuk, Banyuwangi Regency, East Java, Indonesia", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/3200", - "aperture": "1.6", - "focal_length": "35.0", - "iso": 400 - } - }, - "hash": "7f15396e65408570f54d266dd465e465b8329949991a1bbeaa4f98e6593f8441", - "fileDate": "2023-01-23T06:06:50Z", - "photoDate": "2023-01-23T06:06:50Z", - "sortDate": "2023-01-23T06:06:50Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3d b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3d deleted file mode 100644 index 468a673..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3d +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef3d", - "origFileName": "LFq-HxcWT5E.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4061, - "height": 6091, - "description": "Experimental portrait", - "location": null, - "searchText": " Experimental portrait", - "properties": { - "exif": { - "make": "FUJIFILM", - "model": "X-T3", - "name": "FUJIFILM, X-T3", - "exposure_time": "1/60", - "aperture": "1.0", - "focal_length": "35.0", - "iso": 6400 - } - }, - "hash": "20508a8e387bc6fbc59b597e1affe289b2fda461e3d472af2c83979de58b6815", - "fileDate": "2023-01-23T21:06:37Z", - "photoDate": "2023-01-23T21:06:37Z", - "sortDate": "2023-01-23T21:06:37Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3e b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3e deleted file mode 100644 index 4f6750e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3e +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef3e", - "origFileName": "XpIsQWpxXps.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4236, - "height": 2829, - "description": "a person standing on a balcony of a building", - "location": null, - "searchText": " a person standing on a balcony of a building", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7SM3", - "name": "SONY, ILCE-7SM3", - "exposure_time": "1/60", - "aperture": "5.0", - "focal_length": "85.0", - "iso": 800 - } - }, - "hash": "a7db9b6e45bf43114c6ea9a38a9c0fbc44368ab96aee5687e44c9fe9a3cc8e1f", - "fileDate": "2023-01-25T02:36:35Z", - "photoDate": "2023-01-25T02:36:35Z", - "sortDate": "2023-01-25T02:36:35Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3f b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3f deleted file mode 100644 index 417fbf8..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef3f +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef3f", - "origFileName": "mx3RynEbCV4.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5464, - "height": 3640, - "description": "a winding road in the middle of a snow covered forest", - "location": "Weinheim, Deutschland", - "searchText": " a winding road in the middle of a snow covered forest Weinheim, Deutschland", - "properties": { - "exif": { - "make": "DJI", - "model": "FC3411", - "name": "DJI, FC3411", - "exposure_time": "1/640", - "aperture": "2.8", - "focal_length": "8.4", - "iso": 100 - } - }, - "hash": "07e825f74bd7841abd03e1eda0ffcdb045abd19fcb3ebf2661daec1afc94b85b", - "fileDate": "2023-01-27T22:48:28Z", - "photoDate": "2023-01-27T22:48:28Z", - "sortDate": "2023-01-27T22:48:28Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef40 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef40 deleted file mode 100644 index e702f95..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef40 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef40", - "origFileName": "AecCj5qi88M.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2624, - "height": 3936, - "description": "a man sitting at a desk working on a laptop", - "location": null, - "searchText": " a man sitting at a desk working on a laptop", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7C", - "name": "SONY, ILCE-7C", - "exposure_time": "1/80", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 100 - } - }, - "hash": "e664fd9bd8f9e4d67b7647959134a5152277e4284bb615cbb6d987cc68aeafa4", - "fileDate": "2023-01-28T18:33:13Z", - "photoDate": "2023-01-28T18:33:13Z", - "sortDate": "2023-01-28T18:33:13Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef41 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef41 deleted file mode 100644 index 4fd1889..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef41 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef41", - "origFileName": "TJ3Xl4YgHVs.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4928, - "height": 3264, - "description": "a large body of water surrounded by mountains", - "location": "Norway", - "searchText": " a large body of water surrounded by mountains Norway", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "17228a9a2757e30fbb12a23f2cdc564758dd509c7d947173c4b7d018f9a750cb", - "fileDate": "2023-01-28T19:37:15Z", - "photoDate": "2023-01-28T19:37:15Z", - "sortDate": "2023-01-28T19:37:15Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef42 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef42 deleted file mode 100644 index f3f57e4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef42 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef42", - "origFileName": "SMSpk9fprcU.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 7189, - "height": 4795, - "description": "a woman sitting on a bench in a gym", - "location": null, - "searchText": " a woman sitting on a bench in a gym", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/125", - "aperture": "1.8", - "focal_length": "35.0", - "iso": 320 - } - }, - "hash": "45409a02deead8e49ae06d47eb9d09b49005b688d6016459f358a504ab1b0d87", - "fileDate": "2023-01-29T21:19:53Z", - "photoDate": "2023-01-29T21:19:53Z", - "sortDate": "2023-01-29T21:19:53Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef43 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef43 deleted file mode 100644 index 194966f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6389164637613e9ef43 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6389164637613e9ef43", - "origFileName": "THI4cJ-HGvw.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5833, - "height": 3889, - "description": "a woman riding a horse through the ocean", - "location": "Kwinana, WA, Australia", - "searchText": " a woman riding a horse through the ocean Kwinana, WA, Australia", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/8000", - "aperture": "2.0", - "focal_length": "85.0", - "iso": 50 - } - }, - "hash": "d905df09a4709066722677eff7815a5bbb7939e2406367043f85b7920c2ab544", - "fileDate": "2023-01-30T06:27:38Z", - "photoDate": "2023-01-30T06:27:38Z", - "sortDate": "2023-01-30T06:27:38Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef44 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef44 deleted file mode 100644 index b253f1f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef44 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef44", - "origFileName": "sW8AATej1Xc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "a man kneeling down on top of a green holding a golf club", - "location": null, - "searchText": " a man kneeling down on top of a green holding a golf club", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "ba21e6dadd970312facdd2ce412c311151476de2eedcc1dcc3a6c6f0ae415934", - "fileDate": "2023-01-30T19:46:02Z", - "photoDate": "2023-01-30T19:46:02Z", - "sortDate": "2023-01-30T19:46:02Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef45 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef45 deleted file mode 100644 index 5f2376e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef45 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef45", - "origFileName": "b2873V_RcEw.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3740, - "height": 4675, - "description": "a brown cow standing in a snow covered field", - "location": "Puy-de-Dôme, France", - "searchText": " a brown cow standing in a snow covered field Puy-de-Dôme, France", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "c30c32312c81396af09ab7d49b230d51ee97124b6f2532a2a556db36d71a501b", - "fileDate": "2023-01-31T17:24:52Z", - "photoDate": "2023-01-31T17:24:52Z", - "sortDate": "2023-01-31T17:24:52Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef46 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef46 deleted file mode 100644 index 0ab6fde..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef46 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef46", - "origFileName": "fO7tnJ3t5dY.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3093, - "height": 4640, - "description": "Standing on Hopkins Landing dock in Gibsons, B.C", - "location": "Gibsons, BC, Canada", - "searchText": " Standing on Hopkins Landing dock in Gibsons, B.C Gibsons, BC, Canada", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R7", - "name": "Canon, EOS R7", - "exposure_time": "1/50", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 250 - } - }, - "hash": "681d21f0d674a54acf2881f51f4ee6f721a2a9b288ba0f3e261e16a2d48c8fac", - "fileDate": "2023-01-31T20:27:28Z", - "photoDate": "2023-01-31T20:27:28Z", - "sortDate": "2023-01-31T20:27:28Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef47 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef47 deleted file mode 100644 index e43bc2f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef47 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef47", - "origFileName": "Wlvi9rtFOm8.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4419, - "height": 6628, - "description": "a fountain in the middle of a courtyard", - "location": null, - "searchText": " a fountain in the middle of a courtyard", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M4", - "name": "SONY, ILCE-7M4", - "exposure_time": "1/400", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 125 - } - }, - "hash": "64743a92a61a587730ceb444ccfcb535126315e993ae2932224a0716aa544c09", - "fileDate": "2023-02-01T17:35:29Z", - "photoDate": "2023-02-01T17:35:29Z", - "sortDate": "2023-02-01T17:35:29Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef48 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef48 deleted file mode 100644 index a71e988..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef48 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef48", - "origFileName": "ZTXnyniUaLY.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6720, - "height": 4480, - "description": "a man with a beard wearing a hoodie", - "location": "Flint, Flint, United States", - "searchText": " a man with a beard wearing a hoodie Flint, Flint, United States", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R", - "name": "Canon, EOS R", - "exposure_time": "1/100", - "aperture": "3.2", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "e6752c9bee24f7427dd85bfc45f0ec9582c951fb37ebfaef9e01950ad1a22a94", - "fileDate": "2023-02-02T07:09:20Z", - "photoDate": "2023-02-02T07:09:20Z", - "sortDate": "2023-02-02T07:09:20Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef49 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef49 deleted file mode 100644 index 6e5d82e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef49 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef49", - "origFileName": "16i3KFIlR7I.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6890, - "height": 9504, - "description": "mobina", - "location": null, - "searchText": " mobina", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7RM4", - "name": "SONY, ILCE-7RM4", - "exposure_time": "1/80", - "aperture": "3.5", - "focal_length": "35.0", - "iso": 250 - } - }, - "hash": "65148c4d1710631331e37a4bff52311bc449d68d5186e7cfbf9e94ffc8b593af", - "fileDate": "2023-02-02T16:56:18Z", - "photoDate": "2023-02-02T16:56:18Z", - "sortDate": "2023-02-02T16:56:18Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4a b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4a deleted file mode 100644 index f9fa8c7..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4a +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef4a", - "origFileName": "Hy37gkO-AYs.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6000, - "height": 4000, - "description": "a snowboarder is going down a snowy mountain", - "location": "Peyragudes, Loudervielle, France", - "searchText": " a snowboarder is going down a snowy mountain Peyragudes, Loudervielle, France", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7C", - "name": "SONY, ILCE-7C", - "exposure_time": "1/2000", - "aperture": "8.0", - "focal_length": "75.0", - "iso": 125 - } - }, - "hash": "3de8a1e8307af46081027e5e8c6714c191b6bdbd522b1a005652a6f29f1b2fb5", - "fileDate": "2023-02-02T19:45:52Z", - "photoDate": "2023-02-02T19:45:52Z", - "sortDate": "2023-02-02T19:45:52Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4b b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4b deleted file mode 100644 index 74ab9a4..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4b +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef4b", - "origFileName": "Ie-uzpPyoro.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "a train traveling down train tracks next to a tunnel", - "location": null, - "searchText": " a train traveling down train tracks next to a tunnel", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/20", - "aperture": "2.8", - "focal_length": "43.2", - "iso": 320 - } - }, - "hash": "9de67bcbd8c2a996081461f66ecc766a494809cd29367d6a169f4a91519044e8", - "fileDate": "2023-02-03T00:26:56Z", - "photoDate": "2023-02-03T00:26:56Z", - "sortDate": "2023-02-03T00:26:56Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4c b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4c deleted file mode 100644 index 31786c6..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c6399164637613e9ef4c +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c6399164637613e9ef4c", - "origFileName": "x6-w01edyk0.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5472, - "height": 3648, - "description": "an aerial view of a beach with rocks and water", - "location": null, - "searchText": " an aerial view of a beach with rocks and water", - "properties": { - "exif": { - "make": "Hasselblad", - "model": "L1D-20c", - "name": "Hasselblad, L1D-20c", - "exposure_time": "1/240", - "aperture": "3.5", - "focal_length": "10.3", - "iso": 100 - } - }, - "hash": "69ca616bccac1c64df31c29278f59fbf55ec09c4dafc4ed8ec1d85371fff73b0", - "fileDate": "2023-02-05T09:22:19Z", - "photoDate": "2023-02-05T09:22:19Z", - "sortDate": "2023-02-05T09:22:19Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4d b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4d deleted file mode 100644 index 53b178d..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4d +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef4d", - "origFileName": "7kpC3MZ5BOg.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5954, - "height": 3590, - "description": "snow covered mountain trees", - "location": "Comox Valley, BC, Canada", - "searchText": " snow covered mountain trees Comox Valley, BC, Canada", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS Rebel T7i", - "name": "Canon, EOS Rebel T7i", - "exposure_time": "1/125", - "aperture": "11.0", - "focal_length": "29.0", - "iso": 400 - } - }, - "hash": "80f447731e9107ec1499c90870b131d27f31b78d5cd1d4cc44e21805da924176", - "fileDate": "2023-02-07T21:57:22Z", - "photoDate": "2023-02-07T21:57:22Z", - "sortDate": "2023-02-07T21:57:22Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4e b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4e deleted file mode 100644 index fea3979..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4e +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef4e", - "origFileName": "NK9qJ9LRyPc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 6240, - "height": 4160, - "description": "a plant in a pot sitting on a table next to a stair case", - "location": null, - "searchText": " a plant in a pot sitting on a table next to a stair case", - "properties": { - "exif": { - "make": "FUJIFILM", - "model": "X100V", - "name": "FUJIFILM, X100V", - "exposure_time": "1/600", - "aperture": "2.2", - "focal_length": "23.0", - "iso": 640 - } - }, - "hash": "0664a7ce86769e3754495f8af1205067d6d5f15715c4cf57f795f85cf6a6dd83", - "fileDate": "2023-02-09T14:49:56Z", - "photoDate": "2023-02-09T14:49:56Z", - "sortDate": "2023-02-09T14:49:56Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4f b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4f deleted file mode 100644 index 5a26247..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef4f +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef4f", - "origFileName": "KSXGw3K3KY0.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 7262, - "height": 4844, - "description": "a close up of a toilet seat with the lid down", - "location": null, - "searchText": " a close up of a toilet seat with the lid down", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/800", - "aperture": null, - "focal_length": null, - "iso": 100 - } - }, - "hash": "ab21d7ac3be13b98855ac39db5e9c9e46c97e848fef2c1876953d8ff586b455e", - "fileDate": "2023-02-10T17:25:56Z", - "photoDate": "2023-02-10T17:25:56Z", - "sortDate": "2023-02-10T17:25:56Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef50 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef50 deleted file mode 100644 index 15e33c0..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef50 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef50", - "origFileName": "-qDgiE5Vyww.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3200, - "height": 2400, - "description": "a living room with a book shelf and a plant", - "location": "Akure, Nigeria", - "searchText": " a living room with a book shelf and a plant Akure, Nigeria", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "c7785d7fa96e932e97bc9c41bc0cf3f017daf77899ae421d848e1a2d9557f15c", - "fileDate": "2023-01-12T22:54:12Z", - "photoDate": "2023-01-12T22:54:12Z", - "sortDate": "2023-01-12T22:54:12Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef51 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef51 deleted file mode 100644 index 8849421..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef51 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef51", - "origFileName": "L0Bte153mM8.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2955, - "height": 2364, - "description": "a herd of horses standing next to each other", - "location": null, - "searchText": " a herd of horses standing next to each other", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "c5c8c7a95c777994e1c7dec0b830e8fc29a6ee25881bb10492897727e02d02bf", - "fileDate": "2023-01-13T14:11:04Z", - "photoDate": "2023-01-13T14:11:04Z", - "sortDate": "2023-01-13T14:11:04Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef52 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef52 deleted file mode 100644 index 30c19ce..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63a9164637613e9ef52 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63a9164637613e9ef52", - "origFileName": "w3Zc6MHmpmY.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 8256, - "height": 5504, - "description": "a sandy beach with grass growing on top of it", - "location": null, - "searchText": " a sandy beach with grass growing on top of it", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON D850", - "name": "NIKON CORPORATION, NIKON D850", - "exposure_time": "1/2000", - "aperture": "6.7", - "focal_length": "50.0", - "iso": 800 - } - }, - "hash": "380179fdd2d496276ae39cd85246045ab73c0e817ee50017fb9afe169c3e92ab", - "fileDate": "2023-01-14T01:32:34Z", - "photoDate": "2023-01-14T01:32:34Z", - "sortDate": "2023-01-14T01:32:34Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef53 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef53 deleted file mode 100644 index fbbac3f..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef53 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef53", - "origFileName": "xCSP0fMNDkE.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4990, - "height": 6653, - "description": "This lonely road is just beautiful", - "location": "Cabrera, 33000, Dominican Republic", - "searchText": " This lonely road is just beautiful Cabrera, 33000, Dominican Republic", - "properties": { - "exif": { - "make": "DJI", - "model": "Mini Pro 3", - "name": "DJI, Mini Pro 3", - "exposure_time": "1/6", - "aperture": "1.7", - "focal_length": "6.7", - "iso": 200 - } - }, - "hash": "9ca378e5cf846c4b78a3cf6eb515558eb62fd8ca88cc2800aa588a0903c464ee", - "fileDate": "2023-01-14T01:48:52Z", - "photoDate": "2023-01-14T01:48:52Z", - "sortDate": "2023-01-14T01:48:52Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef54 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef54 deleted file mode 100644 index c1b2e6c..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef54 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef54", - "origFileName": "a5C4QyqXH7I.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2555, - "height": 3832, - "description": "a man standing in front of a body of water", - "location": null, - "searchText": " a man standing in front of a body of water", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS 6D", - "name": "Canon, EOS 6D", - "exposure_time": "1/200", - "aperture": "2.2", - "focal_length": "85.0", - "iso": 100 - } - }, - "hash": "477f1dbde89658e34813fff1107b5b1dec077f0ef5dd1d56b7e5ef14439932c4", - "fileDate": "2023-01-17T16:20:35Z", - "photoDate": "2023-01-17T16:20:35Z", - "sortDate": "2023-01-17T16:20:35Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef55 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef55 deleted file mode 100644 index 12a8fa3..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef55 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef55", - "origFileName": "WWEFneQ5XAk.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 5464, - "height": 8192, - "description": "journaling bible open to psalm 63 with notes", - "location": null, - "searchText": " journaling bible open to psalm 63 with notes", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/1250", - "aperture": "2.8", - "focal_length": "35.0", - "iso": 800 - } - }, - "hash": "c70b2a0ebd7b97ba256b0e82677a28afd647fb0520c148cd51193f60a927568e", - "fileDate": "2023-01-17T18:25:14Z", - "photoDate": "2023-01-17T18:25:14Z", - "sortDate": "2023-01-17T18:25:14Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef56 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef56 deleted file mode 100644 index 8b66a33..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef56 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef56", - "origFileName": "kp-wF-ZCHRo.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3343, - "height": 4179, - "description": "a building with a clock on the side of it", - "location": "Avilés, España", - "searchText": " a building with a clock on the side of it Avilés, España", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON D7500", - "name": "NIKON CORPORATION, NIKON D7500", - "exposure_time": "1/1000", - "aperture": "4.5", - "focal_length": "35.0", - "iso": 100 - } - }, - "hash": "b7b4fa54dbda2c3218abb48ea4af8620ece637a4af9d08b92dd5850ea3b76696", - "fileDate": "2023-01-18T16:59:18Z", - "photoDate": "2023-01-18T16:59:18Z", - "sortDate": "2023-01-18T16:59:18Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef57 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef57 deleted file mode 100644 index f61f9cc..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef57 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef57", - "origFileName": "fZL3vx4pLuM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "Paros, Greece.", - "location": "Paros, Greece", - "searchText": " Paros, Greece. Paros, Greece", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "0766b644a3f7b5afa8e260a630e2d639db273ecd24fbcfc7f8211930a44405c0", - "fileDate": "2023-01-23T08:21:41Z", - "photoDate": "2023-01-23T08:21:41Z", - "sortDate": "2023-01-23T08:21:41Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef58 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef58 deleted file mode 100644 index 48bf77c..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef58 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef58", - "origFileName": "-do1gkYi21c.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6016, - "description": "🌷", - "location": "Hanover, Hanover, Jamaica", - "searchText": " 🌷 Hanover, Hanover, Jamaica", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "dc5d065ee0d2c8eb01ae14b6c2b82014b810beaec4d8bd1c0bc3aa48f702696e", - "fileDate": "2023-01-23T18:54:30Z", - "photoDate": "2023-01-23T18:54:30Z", - "sortDate": "2023-01-23T18:54:30Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef59 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef59 deleted file mode 100644 index 6c780a5..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef59 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef59", - "origFileName": "v4x6y2q92LI.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3600, - "height": 5408, - "description": "a building with a clock tower in the middle of a city", - "location": "Denver, CO, USA", - "searchText": " a building with a clock tower in the middle of a city Denver, CO, USA", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 7_2", - "name": "NIKON CORPORATION, NIKON Z 7_2", - "exposure_time": "1/1000", - "aperture": "1.4", - "focal_length": "35.0", - "iso": 125 - } - }, - "hash": "63e0696d186d1851b739552b1d4e1e93b9c3cb5a7843e73793ac48da046befe0", - "fileDate": "2023-01-23T18:59:57Z", - "photoDate": "2023-01-23T18:59:57Z", - "sortDate": "2023-01-23T18:59:57Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef5a b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef5a deleted file mode 100644 index 0473323..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef5a +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef5a", - "origFileName": "bwOklw2MKXc.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3961, - "height": 5942, - "description": "Experimental portrait", - "location": null, - "searchText": " Experimental portrait", - "properties": { - "exif": { - "make": "FUJIFILM", - "model": "X-T3", - "name": "FUJIFILM, X-T3", - "exposure_time": "1/280", - "aperture": "1.4", - "focal_length": "35.0", - "iso": 6400 - } - }, - "hash": "553520d9686d63c49ca180fc9169f4a5d7329d5cb4c8b2be4ef205e1ea5797a8", - "fileDate": "2023-01-23T21:06:37Z", - "photoDate": "2023-01-23T21:06:37Z", - "sortDate": "2023-01-23T21:06:37Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef5b b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef5b deleted file mode 100644 index fbfb25e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63b9164637613e9ef5b +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63b9164637613e9ef5b", - "origFileName": "Xby93pk7tOM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4024, - "height": 6048, - "description": "a man with long hair and a beard with tattoos", - "location": null, - "searchText": " a man with long hair and a beard with tattoos", - "properties": { - "exif": { - "make": "NIKON CORPORATION", - "model": "NIKON Z 6", - "name": "NIKON CORPORATION, NIKON Z 6", - "exposure_time": "1/800", - "aperture": "1.4", - "focal_length": "24.0", - "iso": 80 - } - }, - "hash": "f585fe0383fcbfb208b621c4a9dce0835545c465c637c7940cf5a06c843ed1cd", - "fileDate": "2023-01-25T01:09:13Z", - "photoDate": "2023-01-25T01:09:13Z", - "sortDate": "2023-01-25T01:09:13Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5c b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5c deleted file mode 100644 index 0e95d2d..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5c +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef5c", - "origFileName": "UHWLE1rrBng.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3555, - "height": 5244, - "description": "ig - @pilseeta", - "location": "Mārupe, Mārupes novads, Latvia", - "searchText": " ig - @pilseeta Mārupe, Mārupes novads, Latvia", - "properties": { - "exif": { - "make": "Premier", - "model": "AF M-8000", - "name": "Premier, AF M-8000", - "exposure_time": null, - "aperture": null, - "focal_length": "0.0", - "iso": null - } - }, - "hash": "290cc76da5c4528711e36be80c6db2ae341cecd9a50c0f13e1c38614cf834f58", - "fileDate": "2023-01-25T11:09:29Z", - "photoDate": "2023-01-25T11:09:29Z", - "sortDate": "2023-01-25T11:09:29Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5d b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5d deleted file mode 100644 index 4e40fab..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5d +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef5d", - "origFileName": "wfromvPVGhI.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4400, - "height": 6600, - "description": "a mountain covered in snow and trees under a cloudy sky", - "location": null, - "searchText": " a mountain covered in snow and trees under a cloudy sky", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7RM3", - "name": "SONY, ILCE-7RM3", - "exposure_time": "1/250", - "aperture": "5.6", - "focal_length": "70.0", - "iso": 1000 - } - }, - "hash": "51eb0f882a47f162b31e2c01f4ce6d58412557978b4fc825a402c26d2cef67d1", - "fileDate": "2023-01-25T19:01:09Z", - "photoDate": "2023-01-25T19:01:09Z", - "sortDate": "2023-01-25T19:01:09Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5e b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5e deleted file mode 100644 index 99b57fc..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5e +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef5e", - "origFileName": "YpUcxEnAy5k.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 6000, - "description": "a close up of a blue and green object", - "location": null, - "searchText": " a close up of a blue and green object", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS 2000D", - "name": "Canon, EOS 2000D", - "exposure_time": "1/25", - "aperture": "5.6", - "focal_length": "50.0", - "iso": 400 - } - }, - "hash": "305833625ad85adb6778c3bbb512b5a08fe577bf1536c1864b24307e25cd317f", - "fileDate": "2023-01-28T23:35:43Z", - "photoDate": "2023-01-28T23:35:43Z", - "sortDate": "2023-01-28T23:35:43Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5f b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5f deleted file mode 100644 index b3003ee..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef5f +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef5f", - "origFileName": "Ohq7b4-gAmA.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 3557, - "height": 5336, - "description": "a woman sitting on top of a black suitcase", - "location": "Hamburg, Germany", - "searchText": " a woman sitting on top of a black suitcase Hamburg, Germany", - "properties": { - "exif": { - "make": "SONY", - "model": "ILCE-7M3", - "name": "SONY, ILCE-7M3", - "exposure_time": "1/200", - "aperture": "2.5", - "focal_length": "85.0", - "iso": 400 - } - }, - "hash": "11d4ef27322a97821d683746421605385a3b7f2a257cb663a233125a1f4074eb", - "fileDate": "2023-01-29T04:12:02Z", - "photoDate": "2023-01-29T04:12:02Z", - "sortDate": "2023-01-29T04:12:02Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef60 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef60 deleted file mode 100644 index 390fcea..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef60 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef60", - "origFileName": "u5nuXl4_deQ.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4000, - "height": 3000, - "description": "a blue pillow with a bunch of different objects on it", - "location": null, - "searchText": " a blue pillow with a bunch of different objects on it", - "properties": { - "exif": { - "make": null, - "model": null, - "name": null, - "exposure_time": null, - "aperture": null, - "focal_length": null, - "iso": null - } - }, - "hash": "7f76e8f047a9eaea2463e9a3f79429dcb5cdcaf40a16e156fd5020f3e2fdda7a", - "fileDate": "2023-01-29T21:52:42Z", - "photoDate": "2023-01-29T21:52:42Z", - "sortDate": "2023-01-29T21:52:42Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef61 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef61 deleted file mode 100644 index 66bd4a6..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef61 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef61", - "origFileName": "J-Nir7H1j0o.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4635, - "height": 5794, - "description": "a person sitting at a table with a plate of food", - "location": null, - "searchText": " a person sitting at a table with a plate of food", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/125", - "aperture": "4.0", - "focal_length": "35.0", - "iso": 640 - } - }, - "hash": "4374e98d33bcd9e6033738a597653e49d92d3d1fdffce75f4122b8c9728cc0f7", - "fileDate": "2023-01-30T05:16:59Z", - "photoDate": "2023-01-30T05:16:59Z", - "sortDate": "2023-01-30T05:16:59Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef62 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef62 deleted file mode 100644 index 6a127b1..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef62 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef62", - "origFileName": "7oMcrpVYVwM.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 4695, - "height": 5869, - "description": "a woman sitting at a table with a plate of food", - "location": null, - "searchText": " a woman sitting at a table with a plate of food", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R5", - "name": "Canon, EOS R5", - "exposure_time": "1/125", - "aperture": "4.0", - "focal_length": "70.0", - "iso": 200 - } - }, - "hash": "25bc960e37a49183fc5dd8aef81aa53d603565e7bdbb3bf8b0d835c2db5a8ead", - "fileDate": "2023-01-30T05:16:59Z", - "photoDate": "2023-01-30T05:16:59Z", - "sortDate": "2023-01-30T05:16:59Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef63 b/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef63 deleted file mode 100644 index c29292e..0000000 --- a/fixtures/50-assets/files/collections/test-collection/metadata/63e9c63c9164637613e9ef63 +++ /dev/null @@ -1,27 +0,0 @@ -{ - "_id": "63e9c63c9164637613e9ef63", - "origFileName": "rrkau-m9Yec.jpg", - "contentType": "image/jpeg", - "thumbContentType": "image/jpeg", - "displayContentType": "image/jpeg", - "width": 2141, - "height": 3211, - "description": "Sydney Tower Eye", - "location": "悉尼, 悉尼, 澳大利亚", - "searchText": " Sydney Tower Eye 悉尼, 悉尼, 澳大利亚", - "properties": { - "exif": { - "make": "Canon", - "model": " EOS R6", - "name": "Canon, EOS R6", - "exposure_time": "1/2500", - "aperture": "2.8", - "focal_length": "50.0", - "iso": 100 - } - }, - "hash": "76a3438febf8a96a1fd0b1515017dc5ca3d7f3372ee69e84ce4de58cd3327501", - "fileDate": "2023-01-30T05:37:55Z", - "photoDate": "2023-01-30T05:37:55Z", - "sortDate": "2023-01-30T05:37:55Z" -} \ No newline at end of file diff --git a/fixtures/50-assets/files/users/6610f22eb8093839698e1ba4 b/fixtures/50-assets/files/users/6610f22eb8093839698e1ba4 deleted file mode 100644 index 31545f1..0000000 --- a/fixtures/50-assets/files/users/6610f22eb8093839698e1ba4 +++ /dev/null @@ -1,7 +0,0 @@ -{ - "collections": { - "upload": "test-collection", - "default": "test-collection", - "access": [ "test-collection" ] - } -} \ No newline at end of file diff --git a/fixtures/50-assets/files/users/test-user b/fixtures/50-assets/files/users/test-user deleted file mode 100644 index 31545f1..0000000 --- a/fixtures/50-assets/files/users/test-user +++ /dev/null @@ -1,7 +0,0 @@ -{ - "collections": { - "upload": "test-collection", - "default": "test-collection", - "access": [ "test-collection" ] - } -} \ No newline at end of file diff --git a/fixtures/50-assets/assets.js b/fixtures/50-assets/metadata.js similarity index 79% rename from fixtures/50-assets/assets.js rename to fixtures/50-assets/metadata.js index b370daa..333e7f1 100644 --- a/fixtures/50-assets/assets.js +++ b/fixtures/50-assets/metadata.js @@ -1,13 +1,9 @@ - -const assets = [ - +const metadata = [ /* 1 */ { "_id" : ("63e9c6379164637613e9ef32"), "origFileName" : "kBis_RVfGQM.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5349, "height" : 8019, "description" : "a sandy beach with a body of water in the distance", @@ -25,17 +21,16 @@ const assets = [ }, }, "hash" : "25c6d5bd92f9ef375e50b80196030e2903e4b9be896dce8a2fdfe7a8a3c1f365", - "fileDate" : "2023-01-09T04:36:22Z", - "photoDate" : "2023-01-09T04:36:22Z" + "fileDate" : new Date("2023-01-09T04:36:22Z"), + "photoDate" : new Date("2023-01-09T04:36:22Z"), + "sortDate" : new Date("2023-01-09T04:36:22Z"), + "setId": "test-collection" }, - /* 2 */ { "_id" : ("63e9c6379164637613e9ef33"), "origFileName" : "JOvLQVXf9uA.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 6000, "description" : "Portland Fashion Model wearing a gold crown and necklace Portrait taken by Portland Photographer Lance Reis on my Sonya7iii in my photography studio.", @@ -53,17 +48,16 @@ const assets = [ }, }, "hash" : "39f8c3599cfee13335143cbd2e7893ef5fbd1b0454aaa6e7f51c47eadc9366fa", - "fileDate" : "2023-01-09T15:50:46Z", - "photoDate" : "2023-01-09T15:50:46Z" + "fileDate" : new Date("2023-01-09T15:50:46Z"), + "photoDate" : new Date("2023-01-09T15:50:46Z"), + "sortDate" : new Date("2023-01-09T15:50:46Z"), + "setId": "test-collection" }, - /* 3 */ { "_id" : ("63e9c6379164637613e9ef34"), "origFileName" : "p9ttlbi5wig.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3600, "height" : 2400, "description" : "Smiling Catholic priest (jesuit) giving his blessing at New Yearˇs", @@ -81,21 +75,19 @@ const assets = [ }, }, "hash" : "460b3f18faaed8f6841d44f3fd91916fec29b6fc68eaf3fb0cacd55b9f153499", - "fileDate" : "2023-01-12T19:26:04Z", - "photoDate" : "2023-01-12T19:26:04Z" + "fileDate" : new Date("2023-01-12T19:26:04Z"), + "photoDate" : new Date("2023-01-12T19:26:04Z"), + "sortDate" : new Date("2023-01-12T19:26:04Z"), + "setId": "test-collection" }, - /* 4 */ { "_id" : ("63e9c6379164637613e9ef35"), "origFileName" : "rOuTz5d10k8.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4923, "height" : 6154, "description" : "a woman with her hands on her hips", - "location" : null, "searchText" : " a woman with her hands on her hips", "properties" : { "exif" : { @@ -109,21 +101,19 @@ const assets = [ }, }, "hash" : "c3638f7d122c33f10c42f6ed792c7954018f050a35b13d1a2ad123f0fee783b6", - "fileDate" : "2023-01-13T15:21:25Z", - "photoDate" : "2023-01-13T15:21:25Z" + "fileDate" : new Date("2023-01-13T15:21:25Z"), + "photoDate" : new Date("2023-01-13T15:21:25Z"), + "sortDate" : new Date("2023-01-13T15:21:25Z"), + "setId": "test-collection" }, - /* 5 */ { "_id" : ("63e9c6379164637613e9ef36"), "origFileName" : "jZ5WZaD0v30.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5472, "height" : 3648, "description" : "Earthworks Audio - ETHOS - XLR Microphone used for podcasting, gaming, and chatting with friends.", - "location" : null, "searchText" : " Earthworks Audio - ETHOS - XLR Microphone used for podcasting, gaming, and chatting with friends.", "properties" : { "exif" : { @@ -137,21 +127,19 @@ const assets = [ }, }, "hash" : "419726ceacd326c0016991a00f9df1e712cf4798f77357ee5599b2be0d64a3f6", - "fileDate" : "2023-01-13T22:34:00Z", - "photoDate" : "2023-01-13T22:34:00Z" + "fileDate" : new Date("2023-01-13T22:34:00Z"), + "photoDate" : new Date("2023-01-13T22:34:00Z"), + "sortDate" : new Date("2023-01-13T22:34:00Z"), + "setId": "test-collection" }, - /* 6 */ { "_id" : ("63e9c6379164637613e9ef37"), "origFileName" : "To_ZxorMluI.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3667, "height" : 5500, "description" : "a vase with a flower and two eggs on a napkin", - "location" : null, "searchText" : " a vase with a flower and two eggs on a napkin", "properties" : { "exif" : { @@ -165,17 +153,16 @@ const assets = [ }, }, "hash" : "3ccb5904127ed37f291b5979a909366f062f1f97b03d7c5005d0a0c29c9f84d9", - "fileDate" : "2023-01-16T20:21:03Z", - "photoDate" : "2023-01-16T20:21:03Z" + "fileDate" : new Date("2023-01-16T20:21:03Z"), + "photoDate" : new Date("2023-01-16T20:21:03Z"), + "sortDate" : new Date("2023-01-16T20:21:03Z"), + "setId": "test-collection" }, - /* 7 */ { "_id" : ("63e9c6379164637613e9ef38"), "origFileName" : "Drh5FTN_tvU.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4024, "height" : 6048, "description" : "a person walking down a snow covered road", @@ -193,21 +180,19 @@ const assets = [ }, }, "hash" : "d09dc11087b1fb93881ba61c75831e29c39d74191e505e95c82d63a922a3ea87", - "fileDate" : "2023-01-16T21:35:04Z", - "photoDate" : "2023-01-16T21:35:04Z" + "fileDate" : new Date("2023-01-16T21:35:04Z"), + "photoDate" : new Date("2023-01-16T21:35:04Z"), + "sortDate" : new Date("2023-01-16T21:35:04Z"), + "setId": "test-collection" }, - /* 8 */ { "_id" : ("63e9c6379164637613e9ef39"), "origFileName" : "syEwWS2ueXw.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5000, "height" : 7500, "description" : "a tray of cookies with the word love spelled out", - "location" : null, "searchText" : " a tray of cookies with the word love spelled out", "properties" : { "exif" : { @@ -221,17 +206,16 @@ const assets = [ }, }, "hash" : "abb523db3ba1b12c7624b8a5acc341beaa8d0df028d0301f7aacfde528e1c220", - "fileDate" : "2023-01-17T16:33:15Z", - "photoDate" : "2023-01-17T16:33:15Z" + "fileDate" : new Date("2023-01-17T16:33:15Z"), + "photoDate" : new Date("2023-01-17T16:33:15Z"), + "sortDate" : new Date("2023-01-17T16:33:15Z"), + "setId": "test-collection" }, - /* 9 */ { "_id" : ("63e9c6389164637613e9ef3a"), "origFileName" : "hKdlHLDZB_c.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5760, "height" : 3840, "description" : "a man in a hat is looking at the skyline", @@ -249,17 +233,16 @@ const assets = [ }, }, "hash" : "504345bccaa98396af0535e84947ece609ebd361efae26e7cffd35c12fdb2a12", - "fileDate" : "2023-01-19T16:52:05Z", - "photoDate" : "2023-01-19T16:52:05Z" + "fileDate" : new Date("2023-01-19T16:52:05Z"), + "photoDate" : new Date("2023-01-19T16:52:05Z"), + "sortDate" : new Date("2023-01-19T16:52:05Z"), + "setId": "test-collection" }, - /* 10 */ { "_id" : ("63e9c6389164637613e9ef3b"), "origFileName" : "oQ8QAqsWryc.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4578, "height" : 6867, "description" : "Hazy Winter Day", @@ -277,17 +260,16 @@ const assets = [ }, }, "hash" : "5e1cbd39f832e0d67ad4833a91a376e028a560cdc3946b987a987ba8afc7463d", - "fileDate" : "2023-01-22T21:35:05Z", - "photoDate" : "2023-01-22T21:35:05Z" + "fileDate" : new Date("2023-01-22T21:35:05Z"), + "photoDate" : new Date("2023-01-22T21:35:05Z"), + "sortDate" : new Date("2023-01-22T21:35:05Z"), + "setId": "test-collection" }, - /* 11 */ { "_id" : ("63e9c6389164637613e9ef3c"), "origFileName" : "NEmPI4C1D44.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3374, "height" : 4218, "description" : "A walk through Middle-Earth", @@ -305,21 +287,19 @@ const assets = [ }, }, "hash" : "7f15396e65408570f54d266dd465e465b8329949991a1bbeaa4f98e6593f8441", - "fileDate" : "2023-01-23T06:06:50Z", - "photoDate" : "2023-01-23T06:06:50Z" + "fileDate" : new Date("2023-01-23T06:06:50Z"), + "photoDate" : new Date("2023-01-23T06:06:50Z"), + "sortDate" : new Date("2023-01-23T06:06:50Z"), + "setId": "test-collection" }, - /* 12 */ { "_id" : ("63e9c6389164637613e9ef3d"), "origFileName" : "LFq-HxcWT5E.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4061, "height" : 6091, "description" : "Experimental portrait", - "location" : null, "searchText" : " Experimental portrait", "properties" : { "exif" : { @@ -333,21 +313,19 @@ const assets = [ }, }, "hash" : "20508a8e387bc6fbc59b597e1affe289b2fda461e3d472af2c83979de58b6815", - "fileDate" : "2023-01-23T21:06:37Z", - "photoDate" : "2023-01-23T21:06:37Z" + "fileDate" : new Date("2023-01-23T21:06:37Z"), + "photoDate" : new Date("2023-01-23T21:06:37Z"), + "sortDate" : new Date("2023-01-23T21:06:37Z"), + "setId": "test-collection" }, - /* 13 */ { "_id" : ("63e9c6389164637613e9ef3e"), "origFileName" : "XpIsQWpxXps.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4236, "height" : 2829, "description" : "a person standing on a balcony of a building", - "location" : null, "searchText" : " a person standing on a balcony of a building", "properties" : { "exif" : { @@ -361,17 +339,16 @@ const assets = [ }, }, "hash" : "a7db9b6e45bf43114c6ea9a38a9c0fbc44368ab96aee5687e44c9fe9a3cc8e1f", - "fileDate" : "2023-01-25T02:36:35Z", - "photoDate" : "2023-01-25T02:36:35Z" + "fileDate" : new Date("2023-01-25T02:36:35Z"), + "photoDate" : new Date("2023-01-25T02:36:35Z"), + "sortDate" : new Date("2023-01-25T02:36:35Z"), + "setId": "test-collection" }, - /* 14 */ { "_id" : ("63e9c6389164637613e9ef3f"), "origFileName" : "mx3RynEbCV4.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5464, "height" : 3640, "description" : "a winding road in the middle of a snow covered forest", @@ -389,21 +366,19 @@ const assets = [ }, }, "hash" : "07e825f74bd7841abd03e1eda0ffcdb045abd19fcb3ebf2661daec1afc94b85b", - "fileDate" : "2023-01-27T22:48:28Z", - "photoDate" : "2023-01-27T22:48:28Z" + "fileDate" : new Date("2023-01-27T22:48:28Z"), + "photoDate" : new Date("2023-01-27T22:48:28Z"), + "sortDate" : new Date("2023-01-27T22:48:28Z"), + "setId": "test-collection" }, - /* 15 */ { "_id" : ("63e9c6389164637613e9ef40"), "origFileName" : "AecCj5qi88M.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 2624, "height" : 3936, "description" : "a man sitting at a desk working on a laptop", - "location" : null, "searchText" : " a man sitting at a desk working on a laptop", "properties" : { "exif" : { @@ -417,17 +392,16 @@ const assets = [ }, }, "hash" : "e664fd9bd8f9e4d67b7647959134a5152277e4284bb615cbb6d987cc68aeafa4", - "fileDate" : "2023-01-28T18:33:13Z", - "photoDate" : "2023-01-28T18:33:13Z" + "fileDate" : new Date("2023-01-28T18:33:13Z"), + "photoDate" : new Date("2023-01-28T18:33:13Z"), + "sortDate" : new Date("2023-01-28T18:33:13Z"), + "setId": "test-collection" }, - /* 16 */ { "_id" : ("63e9c6389164637613e9ef41"), "origFileName" : "TJ3Xl4YgHVs.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4928, "height" : 3264, "description" : "a large body of water surrounded by mountains", @@ -445,21 +419,19 @@ const assets = [ }, }, "hash" : "17228a9a2757e30fbb12a23f2cdc564758dd509c7d947173c4b7d018f9a750cb", - "fileDate" : "2023-01-28T19:37:15Z", - "photoDate" : "2023-01-28T19:37:15Z" + "fileDate" : new Date("2023-01-28T19:37:15Z"), + "photoDate" : new Date("2023-01-28T19:37:15Z"), + "sortDate" : new Date("2023-01-28T19:37:15Z"), + "setId": "test-collection" }, - /* 17 */ { "_id" : ("63e9c6389164637613e9ef42"), "origFileName" : "SMSpk9fprcU.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 7189, "height" : 4795, "description" : "a woman sitting on a bench in a gym", - "location" : null, "searchText" : " a woman sitting on a bench in a gym", "properties" : { "exif" : { @@ -473,17 +445,16 @@ const assets = [ }, }, "hash" : "45409a02deead8e49ae06d47eb9d09b49005b688d6016459f358a504ab1b0d87", - "fileDate" : "2023-01-29T21:19:53Z", - "photoDate" : "2023-01-29T21:19:53Z" + "fileDate" : new Date("2023-01-29T21:19:53Z"), + "photoDate" : new Date("2023-01-29T21:19:53Z"), + "sortDate" : new Date("2023-01-29T21:19:53Z"), + "setId": "test-collection" }, - /* 18 */ { "_id" : ("63e9c6389164637613e9ef43"), "origFileName" : "THI4cJ-HGvw.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5833, "height" : 3889, "description" : "a woman riding a horse through the ocean", @@ -501,21 +472,19 @@ const assets = [ }, }, "hash" : "d905df09a4709066722677eff7815a5bbb7939e2406367043f85b7920c2ab544", - "fileDate" : "2023-01-30T06:27:38Z", - "photoDate" : "2023-01-30T06:27:38Z" + "fileDate" : new Date("2023-01-30T06:27:38Z"), + "photoDate" : new Date("2023-01-30T06:27:38Z"), + "sortDate" : new Date("2023-01-30T06:27:38Z"), + "setId": "test-collection" }, - /* 19 */ { "_id" : ("63e9c6399164637613e9ef44"), "origFileName" : "sW8AATej1Xc.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 6000, "description" : "a man kneeling down on top of a green holding a golf club", - "location" : null, "searchText" : " a man kneeling down on top of a green holding a golf club", "properties" : { "exif" : { @@ -529,17 +498,16 @@ const assets = [ }, }, "hash" : "ba21e6dadd970312facdd2ce412c311151476de2eedcc1dcc3a6c6f0ae415934", - "fileDate" : "2023-01-30T19:46:02Z", - "photoDate" : "2023-01-30T19:46:02Z" + "fileDate" : new Date("2023-01-30T19:46:02Z"), + "photoDate" : new Date("2023-01-30T19:46:02Z"), + "sortDate" : new Date("2023-01-30T19:46:02Z"), + "setId": "test-collection" }, - /* 20 */ { "_id" : ("63e9c6399164637613e9ef45"), "origFileName" : "b2873V_RcEw.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3740, "height" : 4675, "description" : "a brown cow standing in a snow covered field", @@ -557,17 +525,16 @@ const assets = [ }, }, "hash" : "c30c32312c81396af09ab7d49b230d51ee97124b6f2532a2a556db36d71a501b", - "fileDate" : "2023-01-31T17:24:52Z", - "photoDate" : "2023-01-31T17:24:52Z" + "fileDate" : new Date("2023-01-31T17:24:52Z"), + "photoDate" : new Date("2023-01-31T17:24:52Z"), + "sortDate" : new Date("2023-01-31T17:24:52Z"), + "setId": "test-collection" }, - /* 21 */ { "_id" : ("63e9c6399164637613e9ef46"), "origFileName" : "fO7tnJ3t5dY.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3093, "height" : 4640, "description" : "Standing on Hopkins Landing dock in Gibsons, B.C", @@ -585,21 +552,19 @@ const assets = [ }, }, "hash" : "681d21f0d674a54acf2881f51f4ee6f721a2a9b288ba0f3e261e16a2d48c8fac", - "fileDate" : "2023-01-31T20:27:28Z", - "photoDate" : "2023-01-31T20:27:28Z" + "fileDate" : new Date("2023-01-31T20:27:28Z"), + "photoDate" : new Date("2023-01-31T20:27:28Z"), + "sortDate" : new Date("2023-01-31T20:27:28Z"), + "setId": "test-collection" }, - /* 22 */ { "_id" : ("63e9c6399164637613e9ef47"), "origFileName" : "Wlvi9rtFOm8.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4419, "height" : 6628, "description" : "a fountain in the middle of a courtyard", - "location" : null, "searchText" : " a fountain in the middle of a courtyard", "properties" : { "exif" : { @@ -613,17 +578,16 @@ const assets = [ }, }, "hash" : "64743a92a61a587730ceb444ccfcb535126315e993ae2932224a0716aa544c09", - "fileDate" : "2023-02-01T17:35:29Z", - "photoDate" : "2023-02-01T17:35:29Z" + "fileDate" : new Date("2023-02-01T17:35:29Z"), + "photoDate" : new Date("2023-02-01T17:35:29Z"), + "sortDate" : new Date("2023-02-01T17:35:29Z"), + "setId": "test-collection" }, - /* 23 */ { "_id" : ("63e9c6399164637613e9ef48"), "origFileName" : "ZTXnyniUaLY.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 6720, "height" : 4480, "description" : "a man with a beard wearing a hoodie", @@ -641,21 +605,19 @@ const assets = [ }, }, "hash" : "e6752c9bee24f7427dd85bfc45f0ec9582c951fb37ebfaef9e01950ad1a22a94", - "fileDate" : "2023-02-02T07:09:20Z", - "photoDate" : "2023-02-02T07:09:20Z" + "fileDate" : new Date("2023-02-02T07:09:20Z"), + "photoDate" : new Date("2023-02-02T07:09:20Z"), + "sortDate" : new Date("2023-02-02T07:09:20Z"), + "setId": "test-collection" }, - /* 24 */ { "_id" : ("63e9c6399164637613e9ef49"), "origFileName" : "16i3KFIlR7I.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 6890, "height" : 9504, "description" : "mobina", - "location" : null, "searchText" : " mobina", "properties" : { "exif" : { @@ -669,17 +631,16 @@ const assets = [ }, }, "hash" : "65148c4d1710631331e37a4bff52311bc449d68d5186e7cfbf9e94ffc8b593af", - "fileDate" : "2023-02-02T16:56:18Z", - "photoDate" : "2023-02-02T16:56:18Z" + "fileDate" : new Date("2023-02-02T16:56:18Z"), + "photoDate" : new Date("2023-02-02T16:56:18Z"), + "sortDate" : new Date("2023-02-02T16:56:18Z"), + "setId": "test-collection" }, - /* 25 */ { "_id" : ("63e9c6399164637613e9ef4a"), "origFileName" : "Hy37gkO-AYs.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 6000, "height" : 4000, "description" : "a snowboarder is going down a snowy mountain", @@ -697,21 +658,19 @@ const assets = [ }, }, "hash" : "3de8a1e8307af46081027e5e8c6714c191b6bdbd522b1a005652a6f29f1b2fb5", - "fileDate" : "2023-02-02T19:45:52Z", - "photoDate" : "2023-02-02T19:45:52Z" + "fileDate" : new Date("2023-02-02T19:45:52Z"), + "photoDate" : new Date("2023-02-02T19:45:52Z"), + "sortDate" : new Date("2023-02-02T19:45:52Z"), + "setId": "test-collection" }, - /* 26 */ { "_id" : ("63e9c6399164637613e9ef4b"), "origFileName" : "Ie-uzpPyoro.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 6000, "description" : "a train traveling down train tracks next to a tunnel", - "location" : null, "searchText" : " a train traveling down train tracks next to a tunnel", "properties" : { "exif" : { @@ -725,21 +684,19 @@ const assets = [ }, }, "hash" : "9de67bcbd8c2a996081461f66ecc766a494809cd29367d6a169f4a91519044e8", - "fileDate" : "2023-02-03T00:26:56Z", - "photoDate" : "2023-02-03T00:26:56Z" + "fileDate" : new Date("2023-02-03T00:26:56Z"), + "photoDate" : new Date("2023-02-03T00:26:56Z"), + "sortDate" : new Date("2023-02-03T00:26:56Z"), + "setId": "test-collection" }, - /* 27 */ { "_id" : ("63e9c6399164637613e9ef4c"), "origFileName" : "x6-w01edyk0.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5472, "height" : 3648, "description" : "an aerial view of a beach with rocks and water", - "location" : null, "searchText" : " an aerial view of a beach with rocks and water", "properties" : { "exif" : { @@ -753,17 +710,16 @@ const assets = [ }, }, "hash" : "69ca616bccac1c64df31c29278f59fbf55ec09c4dafc4ed8ec1d85371fff73b0", - "fileDate" : "2023-02-05T09:22:19Z", - "photoDate" : "2023-02-05T09:22:19Z" + "fileDate" : new Date("2023-02-05T09:22:19Z"), + "photoDate" : new Date("2023-02-05T09:22:19Z"), + "sortDate" : new Date("2023-02-05T09:22:19Z"), + "setId": "test-collection" }, - /* 28 */ { "_id" : ("63e9c63a9164637613e9ef4d"), "origFileName" : "7kpC3MZ5BOg.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5954, "height" : 3590, "description" : "snow covered mountain trees", @@ -781,21 +737,19 @@ const assets = [ }, }, "hash" : "80f447731e9107ec1499c90870b131d27f31b78d5cd1d4cc44e21805da924176", - "fileDate" : "2023-02-07T21:57:22Z", - "photoDate" : "2023-02-07T21:57:22Z" + "fileDate" : new Date("2023-02-07T21:57:22Z"), + "photoDate" : new Date("2023-02-07T21:57:22Z"), + "sortDate" : new Date("2023-02-07T21:57:22Z"), + "setId": "test-collection" }, - /* 29 */ { "_id" : ("63e9c63a9164637613e9ef4e"), "origFileName" : "NK9qJ9LRyPc.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 6240, "height" : 4160, "description" : "a plant in a pot sitting on a table next to a stair case", - "location" : null, "searchText" : " a plant in a pot sitting on a table next to a stair case", "properties" : { "exif" : { @@ -809,21 +763,19 @@ const assets = [ }, }, "hash" : "0664a7ce86769e3754495f8af1205067d6d5f15715c4cf57f795f85cf6a6dd83", - "fileDate" : "2023-02-09T14:49:56Z", - "photoDate" : "2023-02-09T14:49:56Z" + "fileDate" : new Date("2023-02-09T14:49:56Z"), + "photoDate" : new Date("2023-02-09T14:49:56Z"), + "sortDate" : new Date("2023-02-09T14:49:56Z"), + "setId": "test-collection" }, - /* 30 */ { "_id" : ("63e9c63a9164637613e9ef4f"), "origFileName" : "KSXGw3K3KY0.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 7262, "height" : 4844, "description" : "a close up of a toilet seat with the lid down", - "location" : null, "searchText" : " a close up of a toilet seat with the lid down", "properties" : { "exif" : { @@ -837,17 +789,16 @@ const assets = [ }, }, "hash" : "ab21d7ac3be13b98855ac39db5e9c9e46c97e848fef2c1876953d8ff586b455e", - "fileDate" : "2023-02-10T17:25:56Z", - "photoDate" : "2023-02-10T17:25:56Z" + "fileDate" : new Date("2023-02-10T17:25:56Z"), + "photoDate" : new Date("2023-02-10T17:25:56Z"), + "sortDate" : new Date("2023-02-10T17:25:56Z"), + "setId": "test-collection" }, - /* 31 */ { "_id" : ("63e9c63a9164637613e9ef50"), "origFileName" : "-qDgiE5Vyww.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3200, "height" : 2400, "description" : "a living room with a book shelf and a plant", @@ -865,21 +816,19 @@ const assets = [ }, }, "hash" : "c7785d7fa96e932e97bc9c41bc0cf3f017daf77899ae421d848e1a2d9557f15c", - "fileDate" : "2023-01-12T22:54:12Z", - "photoDate" : "2023-01-12T22:54:12Z" + "fileDate" : new Date("2023-01-12T22:54:12Z"), + "photoDate" : new Date("2023-01-12T22:54:12Z"), + "sortDate" : new Date("2023-01-12T22:54:12Z"), + "setId": "test-collection" }, - /* 32 */ { "_id" : ("63e9c63a9164637613e9ef51"), "origFileName" : "L0Bte153mM8.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 2955, "height" : 2364, "description" : "a herd of horses standing next to each other", - "location" : null, "searchText" : " a herd of horses standing next to each other", "properties" : { "exif" : { @@ -893,21 +842,19 @@ const assets = [ }, }, "hash" : "c5c8c7a95c777994e1c7dec0b830e8fc29a6ee25881bb10492897727e02d02bf", - "fileDate" : "2023-01-13T14:11:04Z", - "photoDate" : "2023-01-13T14:11:04Z" + "fileDate" : new Date("2023-01-13T14:11:04Z"), + "photoDate" : new Date("2023-01-13T14:11:04Z"), + "sortDate" : new Date("2023-01-13T14:11:04Z"), + "setId": "test-collection" }, - /* 33 */ { "_id" : ("63e9c63a9164637613e9ef52"), "origFileName" : "w3Zc6MHmpmY.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 8256, "height" : 5504, "description" : "a sandy beach with grass growing on top of it", - "location" : null, "searchText" : " a sandy beach with grass growing on top of it", "properties" : { "exif" : { @@ -921,17 +868,16 @@ const assets = [ }, }, "hash" : "380179fdd2d496276ae39cd85246045ab73c0e817ee50017fb9afe169c3e92ab", - "fileDate" : "2023-01-14T01:32:34Z", - "photoDate" : "2023-01-14T01:32:34Z" + "fileDate" : new Date("2023-01-14T01:32:34Z"), + "photoDate" : new Date("2023-01-14T01:32:34Z"), + "sortDate" : new Date("2023-01-14T01:32:34Z"), + "setId": "test-collection" }, - /* 34 */ { "_id" : ("63e9c63b9164637613e9ef53"), "origFileName" : "xCSP0fMNDkE.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4990, "height" : 6653, "description" : "This lonely road is just beautiful", @@ -949,21 +895,19 @@ const assets = [ }, }, "hash" : "9ca378e5cf846c4b78a3cf6eb515558eb62fd8ca88cc2800aa588a0903c464ee", - "fileDate" : "2023-01-14T01:48:52Z", - "photoDate" : "2023-01-14T01:48:52Z" + "fileDate" : new Date("2023-01-14T01:48:52Z"), + "photoDate" : new Date("2023-01-14T01:48:52Z"), + "sortDate" : new Date("2023-01-14T01:48:52Z"), + "setId": "test-collection" }, - /* 35 */ { "_id" : ("63e9c63b9164637613e9ef54"), "origFileName" : "a5C4QyqXH7I.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 2555, "height" : 3832, "description" : "a man standing in front of a body of water", - "location" : null, "searchText" : " a man standing in front of a body of water", "properties" : { "exif" : { @@ -977,21 +921,19 @@ const assets = [ }, }, "hash" : "477f1dbde89658e34813fff1107b5b1dec077f0ef5dd1d56b7e5ef14439932c4", - "fileDate" : "2023-01-17T16:20:35Z", - "photoDate" : "2023-01-17T16:20:35Z" + "fileDate" : new Date("2023-01-17T16:20:35Z"), + "photoDate" : new Date("2023-01-17T16:20:35Z"), + "sortDate" : new Date("2023-01-17T16:20:35Z"), + "setId": "test-collection" }, - /* 36 */ { "_id" : ("63e9c63b9164637613e9ef55"), "origFileName" : "WWEFneQ5XAk.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 5464, "height" : 8192, "description" : "journaling bible open to psalm 63 with notes", - "location" : null, "searchText" : " journaling bible open to psalm 63 with notes", "properties" : { "exif" : { @@ -1005,17 +947,16 @@ const assets = [ }, }, "hash" : "c70b2a0ebd7b97ba256b0e82677a28afd647fb0520c148cd51193f60a927568e", - "fileDate" : "2023-01-17T18:25:14Z", - "photoDate" : "2023-01-17T18:25:14Z" + "fileDate" : new Date("2023-01-17T18:25:14Z"), + "photoDate" : new Date("2023-01-17T18:25:14Z"), + "sortDate" : new Date("2023-01-17T18:25:14Z"), + "setId": "test-collection" }, - /* 37 */ { "_id" : ("63e9c63b9164637613e9ef56"), "origFileName" : "kp-wF-ZCHRo.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3343, "height" : 4179, "description" : "a building with a clock on the side of it", @@ -1033,17 +974,16 @@ const assets = [ }, }, "hash" : "b7b4fa54dbda2c3218abb48ea4af8620ece637a4af9d08b92dd5850ea3b76696", - "fileDate" : "2023-01-18T16:59:18Z", - "photoDate" : "2023-01-18T16:59:18Z" + "fileDate" : new Date("2023-01-18T16:59:18Z"), + "photoDate" : new Date("2023-01-18T16:59:18Z"), + "sortDate" : new Date("2023-01-18T16:59:18Z"), + "setId": "test-collection" }, - /* 38 */ { "_id" : ("63e9c63b9164637613e9ef57"), "origFileName" : "fZL3vx4pLuM.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 6000, "description" : "Paros, Greece.", @@ -1061,17 +1001,16 @@ const assets = [ }, }, "hash" : "0766b644a3f7b5afa8e260a630e2d639db273ecd24fbcfc7f8211930a44405c0", - "fileDate" : "2023-01-23T08:21:41Z", - "photoDate" : "2023-01-23T08:21:41Z" + "fileDate" : new Date("2023-01-23T08:21:41Z"), + "photoDate" : new Date("2023-01-23T08:21:41Z"), + "sortDate" : new Date("2023-01-23T08:21:41Z"), + "setId": "test-collection" }, - /* 39 */ { "_id" : ("63e9c63b9164637613e9ef58"), "origFileName" : "-do1gkYi21c.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 6016, "description" : "🌷", @@ -1089,17 +1028,16 @@ const assets = [ }, }, "hash" : "dc5d065ee0d2c8eb01ae14b6c2b82014b810beaec4d8bd1c0bc3aa48f702696e", - "fileDate" : "2023-01-23T18:54:30Z", - "photoDate" : "2023-01-23T18:54:30Z" + "fileDate" : new Date("2023-01-23T18:54:30Z"), + "photoDate" : new Date("2023-01-23T18:54:30Z"), + "sortDate" : new Date("2023-01-23T18:54:30Z"), + "setId": "test-collection" }, - /* 40 */ { "_id" : ("63e9c63b9164637613e9ef59"), "origFileName" : "v4x6y2q92LI.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3600, "height" : 5408, "description" : "a building with a clock tower in the middle of a city", @@ -1117,21 +1055,19 @@ const assets = [ }, }, "hash" : "63e0696d186d1851b739552b1d4e1e93b9c3cb5a7843e73793ac48da046befe0", - "fileDate" : "2023-01-23T18:59:57Z", - "photoDate" : "2023-01-23T18:59:57Z" + "fileDate" : new Date("2023-01-23T18:59:57Z"), + "photoDate" : new Date("2023-01-23T18:59:57Z"), + "sortDate" : new Date("2023-01-23T18:59:57Z"), + "setId": "test-collection" }, - /* 41 */ { "_id" : ("63e9c63b9164637613e9ef5a"), "origFileName" : "bwOklw2MKXc.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3961, "height" : 5942, "description" : "Experimental portrait", - "location" : null, "searchText" : " Experimental portrait", "properties" : { "exif" : { @@ -1145,21 +1081,19 @@ const assets = [ }, }, "hash" : "553520d9686d63c49ca180fc9169f4a5d7329d5cb4c8b2be4ef205e1ea5797a8", - "fileDate" : "2023-01-23T21:06:37Z", - "photoDate" : "2023-01-23T21:06:37Z" + "fileDate" : new Date("2023-01-23T21:06:37Z"), + "photoDate" : new Date("2023-01-23T21:06:37Z"), + "sortDate" : new Date("2023-01-23T21:06:37Z"), + "setId": "test-collection" }, - /* 42 */ { "_id" : ("63e9c63b9164637613e9ef5b"), "origFileName" : "Xby93pk7tOM.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4024, "height" : 6048, "description" : "a man with long hair and a beard with tattoos", - "location" : null, "searchText" : " a man with long hair and a beard with tattoos", "properties" : { "exif" : { @@ -1173,17 +1107,16 @@ const assets = [ }, }, "hash" : "f585fe0383fcbfb208b621c4a9dce0835545c465c637c7940cf5a06c843ed1cd", - "fileDate" : "2023-01-25T01:09:13Z", - "photoDate" : "2023-01-25T01:09:13Z" + "fileDate" : new Date("2023-01-25T01:09:13Z"), + "photoDate" : new Date("2023-01-25T01:09:13Z"), + "sortDate" : new Date("2023-01-25T01:09:13Z"), + "setId": "test-collection" }, - /* 43 */ { "_id" : ("63e9c63c9164637613e9ef5c"), "origFileName" : "UHWLE1rrBng.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3555, "height" : 5244, "description" : "ig - @pilseeta", @@ -1201,21 +1134,19 @@ const assets = [ }, }, "hash" : "290cc76da5c4528711e36be80c6db2ae341cecd9a50c0f13e1c38614cf834f58", - "fileDate" : "2023-01-25T11:09:29Z", - "photoDate" : "2023-01-25T11:09:29Z" + "fileDate" : new Date("2023-01-25T11:09:29Z"), + "photoDate" : new Date("2023-01-25T11:09:29Z"), + "sortDate" : new Date("2023-01-25T11:09:29Z"), + "setId": "test-collection" }, - /* 44 */ { "_id" : ("63e9c63c9164637613e9ef5d"), "origFileName" : "wfromvPVGhI.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4400, "height" : 6600, "description" : "a mountain covered in snow and trees under a cloudy sky", - "location" : null, "searchText" : " a mountain covered in snow and trees under a cloudy sky", "properties" : { "exif" : { @@ -1229,21 +1160,19 @@ const assets = [ }, }, "hash" : "51eb0f882a47f162b31e2c01f4ce6d58412557978b4fc825a402c26d2cef67d1", - "fileDate" : "2023-01-25T19:01:09Z", - "photoDate" : "2023-01-25T19:01:09Z" + "fileDate" : new Date("2023-01-25T19:01:09Z"), + "photoDate" : new Date("2023-01-25T19:01:09Z"), + "sortDate" : new Date("2023-01-25T19:01:09Z"), + "setId": "test-collection" }, - /* 45 */ { "_id" : ("63e9c63c9164637613e9ef5e"), "origFileName" : "YpUcxEnAy5k.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 6000, "description" : "a close up of a blue and green object", - "location" : null, "searchText" : " a close up of a blue and green object", "properties" : { "exif" : { @@ -1257,17 +1186,16 @@ const assets = [ }, }, "hash" : "305833625ad85adb6778c3bbb512b5a08fe577bf1536c1864b24307e25cd317f", - "fileDate" : "2023-01-28T23:35:43Z", - "photoDate" : "2023-01-28T23:35:43Z" + "fileDate" : new Date("2023-01-28T23:35:43Z"), + "photoDate" : new Date("2023-01-28T23:35:43Z"), + "sortDate" : new Date("2023-01-28T23:35:43Z"), + "setId": "test-collection" }, - /* 46 */ { "_id" : ("63e9c63c9164637613e9ef5f"), "origFileName" : "Ohq7b4-gAmA.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 3557, "height" : 5336, "description" : "a woman sitting on top of a black suitcase", @@ -1285,21 +1213,19 @@ const assets = [ }, }, "hash" : "11d4ef27322a97821d683746421605385a3b7f2a257cb663a233125a1f4074eb", - "fileDate" : "2023-01-29T04:12:02Z", - "photoDate" : "2023-01-29T04:12:02Z" + "fileDate" : new Date("2023-01-29T04:12:02Z"), + "photoDate" : new Date("2023-01-29T04:12:02Z"), + "sortDate" : new Date("2023-01-29T04:12:02Z"), + "setId": "test-collection" }, - /* 47 */ { "_id" : ("63e9c63c9164637613e9ef60"), "origFileName" : "u5nuXl4_deQ.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4000, "height" : 3000, "description" : "a blue pillow with a bunch of different objects on it", - "location" : null, "searchText" : " a blue pillow with a bunch of different objects on it", "properties" : { "exif" : { @@ -1313,21 +1239,19 @@ const assets = [ }, }, "hash" : "7f76e8f047a9eaea2463e9a3f79429dcb5cdcaf40a16e156fd5020f3e2fdda7a", - "fileDate" : "2023-01-29T21:52:42Z", - "photoDate" : "2023-01-29T21:52:42Z" + "fileDate" : new Date("2023-01-29T21:52:42Z"), + "photoDate" : new Date("2023-01-29T21:52:42Z"), + "sortDate" : new Date("2023-01-29T21:52:42Z"), + "setId": "test-collection" }, - /* 48 */ { "_id" : ("63e9c63c9164637613e9ef61"), "origFileName" : "J-Nir7H1j0o.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4635, "height" : 5794, "description" : "a person sitting at a table with a plate of food", - "location" : null, "searchText" : " a person sitting at a table with a plate of food", "properties" : { "exif" : { @@ -1341,21 +1265,19 @@ const assets = [ }, }, "hash" : "4374e98d33bcd9e6033738a597653e49d92d3d1fdffce75f4122b8c9728cc0f7", - "fileDate" : "2023-01-30T05:16:59Z", - "photoDate" : "2023-01-30T05:16:59Z" + "fileDate" : new Date("2023-01-30T05:16:59Z"), + "photoDate" : new Date("2023-01-30T05:16:59Z"), + "sortDate" : new Date("2023-01-30T05:16:59Z"), + "setId": "test-collection" }, - /* 49 */ { "_id" : ("63e9c63c9164637613e9ef62"), "origFileName" : "7oMcrpVYVwM.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 4695, "height" : 5869, "description" : "a woman sitting at a table with a plate of food", - "location" : null, "searchText" : " a woman sitting at a table with a plate of food", "properties" : { "exif" : { @@ -1369,17 +1291,16 @@ const assets = [ }, }, "hash" : "25bc960e37a49183fc5dd8aef81aa53d603565e7bdbb3bf8b0d835c2db5a8ead", - "fileDate" : "2023-01-30T05:16:59Z", - "photoDate" : "2023-01-30T05:16:59Z" + "fileDate" : new Date("2023-01-30T05:16:59Z"), + "photoDate" : new Date("2023-01-30T05:16:59Z"), + "sortDate" : new Date("2023-01-30T05:16:59Z"), + "setId": "test-collection" }, - /* 50 */ { "_id" : ("63e9c63c9164637613e9ef63"), "origFileName" : "rrkau-m9Yec.jpg", "contentType" : "image/jpeg", - "thumbContentType" : "image/jpeg", - "displayContentType" : "image/jpeg", "width" : 2141, "height" : 3211, "description" : "Sydney Tower Eye", @@ -1397,38 +1318,11 @@ const assets = [ }, }, "hash" : "76a3438febf8a96a1fd0b1515017dc5ca3d7f3372ee69e84ce4de58cd3327501", - "fileDate" : "2023-01-30T05:37:55Z", - "photoDate" : "2023-01-30T05:37:55Z" + "fileDate" : new Date("2023-01-30T05:37:55Z"), + "photoDate" : new Date("2023-01-30T05:37:55Z"), + "sortDate" : new Date("2023-01-30T05:37:55Z"), + "setId": "test-collection" }, - ]; -const fs = require("fs"); - -const maxLength = String(assets.length).length; - -for (let i = 0; i < assets.length; i++) { - const asset = assets[i]; - - // asset.sortDate = asset.photoDate; - // fs.writeFileSync(`./files/metadata/${asset._id}`, JSON.stringify(asset, null, 2)); - - // fs.writeFileSync(`./files/display/${asset._id}.info`, JSON.stringify({ contentType: "image/jpeg" }, null, 2)); - - - - // for (let i = 1; i <= maxNumber; i++) { - - const clone = { ...asset }; - delete clone._id; - - const paddedNumber = String(i).padStart(maxLength, '0'); - fs.writeFileSync(`./files/collections/test-collection/journal/${paddedNumber}`, JSON.stringify({ - clientId: "some-client-id", - id: asset._id, - op: { - type: "set", - fields: clone, - }, - }, null, 2)); -} \ No newline at end of file +module.exports = metadata; \ No newline at end of file diff --git a/fixtures/50-assets/sets.js b/fixtures/50-assets/sets.js new file mode 100644 index 0000000..297fdef --- /dev/null +++ b/fixtures/50-assets/sets.js @@ -0,0 +1,11 @@ +const sets = [ + { + "_id": "test-collection", + "name": "Test collection", + "owners": [ + "test-user" + ] + } +] + +module.exports = sets; \ No newline at end of file diff --git a/fixtures/50-assets/users.js b/fixtures/50-assets/users.js new file mode 100644 index 0000000..83bd6e6 --- /dev/null +++ b/fixtures/50-assets/users.js @@ -0,0 +1,12 @@ +const users = [ + { + "_id": "test-user", + "sets": { + "upload": "test-collection", + "default": "test-collection", + "access": [ "test-collection" ] + } + } +] + +module.exports = users; \ No newline at end of file diff --git a/fixtures/no-assets/files/collections/test-collection/metadata.json b/fixtures/no-assets/files/collections/test-collection/metadata.json deleted file mode 100644 index 7b91f2c..0000000 --- a/fixtures/no-assets/files/collections/test-collection/metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Test collection", - "owners": [ - "test-user" - ] -} \ No newline at end of file diff --git a/fixtures/no-assets/files/users/6610f22eb8093839698e1ba4 b/fixtures/no-assets/files/users/6610f22eb8093839698e1ba4 deleted file mode 100644 index 31545f1..0000000 --- a/fixtures/no-assets/files/users/6610f22eb8093839698e1ba4 +++ /dev/null @@ -1,7 +0,0 @@ -{ - "collections": { - "upload": "test-collection", - "default": "test-collection", - "access": [ "test-collection" ] - } -} \ No newline at end of file diff --git a/fixtures/no-assets/files/users/test-user b/fixtures/no-assets/files/users/test-user deleted file mode 100644 index 31545f1..0000000 --- a/fixtures/no-assets/files/users/test-user +++ /dev/null @@ -1,7 +0,0 @@ -{ - "collections": { - "upload": "test-collection", - "default": "test-collection", - "access": [ "test-collection" ] - } -} \ No newline at end of file diff --git a/fixtures/no-assets/sets.js b/fixtures/no-assets/sets.js new file mode 100644 index 0000000..297fdef --- /dev/null +++ b/fixtures/no-assets/sets.js @@ -0,0 +1,11 @@ +const sets = [ + { + "_id": "test-collection", + "name": "Test collection", + "owners": [ + "test-user" + ] + } +] + +module.exports = sets; \ No newline at end of file diff --git a/fixtures/no-assets/users.js b/fixtures/no-assets/users.js new file mode 100644 index 0000000..83bd6e6 --- /dev/null +++ b/fixtures/no-assets/users.js @@ -0,0 +1,12 @@ +const users = [ + { + "_id": "test-user", + "sets": { + "upload": "test-collection", + "default": "test-collection", + "access": [ "test-collection" ] + } + } +] + +module.exports = users; \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 7d34da3..0161150 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -53,7 +53,6 @@ "ts-loader": "^9.5.1", "typescript": "^4.9.5", "user-interface": "workspace:*", - "database": "workspace:*", "wait-port": "^1.0.4", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 5e88612..c4f0c16 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -1,37 +1,21 @@ import React, { useRef } from "react"; import { BrowserRouter } from "react-router-dom"; -import { Main, ApiContextProvider, UploadContextProvider, AuthContextProvider, isProduction, GalleryContextProvider, useLocalGallerySource, useLocalGallerySink, useIndexeddbGallerySource, useIndexeddbGallerySink, useCloudGallerySource, useCloudGallerySink, IndexeddbContextProvider, DbSyncContextProvider, useIndexeddb, useApi } from "user-interface"; +import { Main, ApiContextProvider, UploadContextProvider, AuthContextProvider, isProduction, GalleryContextProvider, useLocalGallerySource, useLocalGallerySink, IndexeddbContextProvider, DbSyncContextProvider, useIndexeddb, useApi, PersistentQueue, IAssetUploadRecord, IAssetUpdateRecord } from "user-interface"; import { Auth0Provider } from "@auth0/auth0-react"; -import { IIndexeddbDatabase, IPersistentQueue, PersistentQueue, IAssetUploadRecord, IAssetUpdateRecord, CloudDatabases } from "database"; import dayjs from "dayjs"; function GallerySetup() { - const api = useApi(); - const cloudDatabases = new CloudDatabases(api); - const indexeddb = useIndexeddb(); - const indexeddbSource = useIndexeddbGallerySource({ indexeddbDatabases: indexeddb.databases }); - const indexeddbSink = useIndexeddbGallerySink({ indexeddbDatabases: indexeddb.databases }); - - const cloudSource = useCloudGallerySource({ api }); - const cloudSink = useCloudGallerySink({ api }); - const userDatabase = indexeddb.databases.database("user"); const outgoingAssetUploadQueue = useRef>(new PersistentQueue(userDatabase, "outgoing-asset-upload")); const outgoingAssetUpdateQueue = useRef>(new PersistentQueue(userDatabase, "outgoing-asset-update")); - const localSource = useLocalGallerySource({ indexeddbSource, indexeddbSink, cloudSource }); - const localSink = useLocalGallerySink({ indexeddbSink, outgoingAssetUploadQueue: outgoingAssetUploadQueue.current, outgoingAssetUpdateQueue: outgoingAssetUpdateQueue.current }); + const localSource = useLocalGallerySource({ indexeddbDatabases: indexeddb.databases, api }); + const localSink = useLocalGallerySink({ outgoingAssetUploadQueue: outgoingAssetUploadQueue.current, outgoingAssetUpdateQueue: outgoingAssetUpdateQueue.current, indexeddbDatabases: indexeddb.databases }); return ( diff --git a/mobile/frontend/package.json b/mobile/frontend/package.json index 20f1e69..801ee74 100644 --- a/mobile/frontend/package.json +++ b/mobile/frontend/package.json @@ -54,6 +54,6 @@ "react-dom": "^18.2.0", "react-router-dom": "^6.4.1", "user-interface": "workspace:*", - "database": "workspace:*" + "defs": "workspace:*" } } diff --git a/mobile/frontend/src/app.tsx b/mobile/frontend/src/app.tsx index 8a38dd7..bcfb536 100644 --- a/mobile/frontend/src/app.tsx +++ b/mobile/frontend/src/app.tsx @@ -1,10 +1,9 @@ import React, { useEffect, useRef } from "react"; import { HashRouter } from "react-router-dom"; -import { ApiContextProvider, AuthContextProvider, DbSyncContextProvider, GalleryContextProvider, IndexeddbContextProvider, Main, UploadContextProvider, useApi, useCloudGallerySink, useCloudGallerySource, useIndexeddb, useIndexeddbGallerySink, useIndexeddbGallerySource, useLocalGallerySink, useLocalGallerySource } from "user-interface"; +import { ApiContextProvider, AuthContextProvider, DbSyncContextProvider, GalleryContextProvider, IAssetUpdateRecord, IAssetUploadRecord, IndexeddbContextProvider, Main, PersistentQueue, UploadContextProvider, useApi, useIndexeddb, useLocalGallerySink, useLocalGallerySource } from "user-interface"; import { Auth0Provider, useAuth0 } from "@auth0/auth0-react"; import { App as CapacitorApp } from "@capacitor/app"; import { Browser } from "@capacitor/browser"; -import { CloudDatabases, PersistentQueue, IAssetUploadRecord, IAssetUpdateRecord } from "database"; import { ComputerPage } from "./pages/computer"; import { ScanContextProvider } from "./context/scan-context"; import dayjs from "dayjs"; @@ -12,31 +11,17 @@ import dayjs from "dayjs"; function GallerySetup() { const api = useApi(); - const cloudDatabases = new CloudDatabases(api); - const indexeddb = useIndexeddb(); - const indexeddbSource = useIndexeddbGallerySource({ indexeddbDatabases: indexeddb.databases }); - const indexeddbSink = useIndexeddbGallerySink({ indexeddbDatabases: indexeddb.databases }); - - const cloudSource = useCloudGallerySource({ api }); - const cloudSink = useCloudGallerySink({ api }); - const userDatabase = indexeddb.databases.database("user"); const outgoingAssetUploadQueue = useRef>(new PersistentQueue(userDatabase, "outgoing-asset-upload")); const outgoingAssetUpdateQueue = useRef>(new PersistentQueue(userDatabase, "outgoing-asset-update")); - const localSource = useLocalGallerySource({ indexeddbSource, indexeddbSink, cloudSource }); - const localSink = useLocalGallerySink({ indexeddbSink, outgoingAssetUploadQueue: outgoingAssetUploadQueue.current, outgoingAssetUpdateQueue: outgoingAssetUpdateQueue.current }); + const localSource = useLocalGallerySource({ indexeddbDatabases: indexeddb.databases, api }); + const localSink = useLocalGallerySink({ outgoingAssetUploadQueue: outgoingAssetUploadQueue.current, outgoingAssetUpdateQueue: outgoingAssetUpdateQueue.current, indexeddbDatabases: indexeddb.databases }); return ( diff --git a/mobile/frontend/src/context/source/computer-gallery-source-context.tsx b/mobile/frontend/src/context/source/computer-gallery-source-context.tsx index eba9c88..c1abbd4 100644 --- a/mobile/frontend/src/context/source/computer-gallery-source-context.tsx +++ b/mobile/frontend/src/context/source/computer-gallery-source-context.tsx @@ -2,8 +2,9 @@ // Provides a source of assets for the gallery from the local computer. // +import { IAssetData, IAssetSource } from "user-interface"; +import { IAsset } from "defs"; import { useScan } from "../scan-context"; -import { IAsset, IAssetData, IAssetSource, IPage } from "database"; // // Use the "computer source" in a component. @@ -18,11 +19,8 @@ export function useComputerGallerySource(): IAssetSource { // // Loads metadata for all assets. // - async function loadAssets(collectionId: string, max: number, next?: string): Promise> { - return { - records: [], - next: undefined, - }; + async function loadAssets(collectionId: string): Promise { + return []; } // @@ -41,7 +39,6 @@ export function useComputerGallerySource(): IAssetSource { } return { - isInitialised: false, loadAssets, mapHashToAssets, loadAsset, diff --git a/packages/database/src/defs/page.ts b/packages/database/src/defs/page.ts deleted file mode 100644 index 47ed081..0000000 --- a/packages/database/src/defs/page.ts +++ /dev/null @@ -1,14 +0,0 @@ -// -// A page of records from the database. -// -export interface IPage { - // - // Array of records in the page. - // - records: RecordT[]; - - // - // Continuation token for the next page. - // - next?: string; -} diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts deleted file mode 100644 index 764a113..0000000 --- a/packages/database/src/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -export * from "./defs/ops"; -export * from "./defs/asset"; -export * from "./defs/asset-data"; -export * from "./defs/user"; -export * from "./defs/page"; -export * from "./defs/hash-record"; -export * from "./lib/apply-operation"; -export * from "./lib/get-journal"; -export * from "./lib/database-collection"; -export * from "./lib/database"; -export * from "./lib/databases"; -export * from "./lib/timestamp"; -export * from "./lib/asset-sink"; -export * from "./lib/asset-source"; -export * from "./lib/uuid"; -export * from "./lib/api"; -export * from "./lib/sync/asset-upload-record"; -export * from "./lib/sync/asset-update-record"; -export * from "./lib/sync/update-id-record"; -export * from "./lib/sync/persistent-queue"; -export * from "./lib/sync/sync-initial"; -export * from "./lib/sync/sync-outgoing"; -export * from "./lib/sync/sync-incoming"; -export * from "./lib/sync/asset-update-record"; -export * from "./lib/sync/asset-upload-record"; -export * from "./lib/sync/persistent-queue"; -export * as indexeddb from "./lib/indexeddb/indexeddb"; -export * from "./lib/indexeddb/indexeddb-database-collection"; -export * from "./lib/indexeddb/indexeddb-database"; -export * from "./lib/indexeddb/indexeddb-databases"; -export * from "./lib/storage/storage"; -export * from "./lib/storage/storage-directory"; -export * from "./lib/storagedb/storage-database-collection"; -export * from "./lib/storagedb/storage-database"; -export * from "./lib/storagedb/storage-databases"; -export * from "./lib/cloud/cloud-database-collection"; -export * from "./lib/cloud/cloud-database"; -export * from "./lib/cloud/cloud-databases"; diff --git a/packages/database/src/lib/api.ts b/packages/database/src/lib/api.ts deleted file mode 100644 index 551051f..0000000 --- a/packages/database/src/lib/api.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { IAsset } from "../defs/asset"; -import { IAssetData } from "../defs/asset-data"; -import { IDatabaseOp } from "../defs/ops"; -import { IPage } from "../defs/page"; -import { IUser } from "../defs/user"; -import { IJournalResult } from "./get-journal"; - -// -// The result of the get assets request. -// -export interface IGetAssetsResult { - // - // Assets returned from this request. - // Set to an empty array if no more assets. - // - assets: IAsset[]; - - // - // Continuation token for the next page of assets. - // Set to undefined when no more pages. - // - next?: string; -} - -// -// Client-side interface to the Photosphere API. -// -export interface IApi { - - // - // Set to true once the api is ready to use. - // - isInitialised: boolean; - - // - // Loads the user's details. - // - getUser(): Promise; - - // - // Retreives the latest update id for a collection. - // - getLatestUpdateId(collectionId: string): Promise; - - // - // Retreives the data for an asset from the backend. - // - getAsset(collectionId: string, assetId: string, assetType: string): Promise; - - // - // Uploads an asset to the backend. - // - uploadSingleAsset(collectionId: string, assetId: string, assetType: string, assetData: IAssetData): Promise; - - // - // Submits database operations to the cloud. - // - submitOperations(ops: IDatabaseOp[]): Promise; - - // - // Gets the journal of operations that have been applied to the database. - // - getJournal(collectionId: string, lastUpdateId?: string): Promise; - - // - // Sets a new record to the database. - // - setOne(databaseName: string, collectionName: string, id: string, record: RecordT): Promise; - - // - // Gets one record by id. - // - getOne(databaseName: string, collectionName: string, id: string): Promise; - - // - // Lists all records in the database. - // - listAll(databaseName: string, collectionName: string, max: number, next?: string): Promise>; - - // - // Gets a page of records from the database. - // - getAll(databaseName: string, collectionName: string, max: number, next?: string): Promise>; - - // - // Deletes a database record. - // - deleteOne(databaseName: string, collectionName: string, recordId: string): Promise; -} \ No newline at end of file diff --git a/packages/database/src/lib/cloud/cloud-database-collection.ts b/packages/database/src/lib/cloud/cloud-database-collection.ts deleted file mode 100644 index 530677d..0000000 --- a/packages/database/src/lib/cloud/cloud-database-collection.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { get } from "http"; -import { IDatabaseCollection } from "../database-collection"; -import { IApi } from "../api"; -import { IPage } from "../../defs/page"; - -export interface ICloudDatabaseCollection extends IDatabaseCollection { -} - -export class CloudDatabaseCollection implements ICloudDatabaseCollection { - - constructor(private databaseName: string, private collectionName: string, private api: IApi) { - } - - // - // Sets a new record to the database. - // - async setOne(recordId: string, record: RecordT): Promise { - await this.api.setOne(this.databaseName, this.collectionName, recordId, record); - } - - // - // Gets one record by id. - // - async getOne(recordId: string): Promise { - return await this.api.getOne(this.databaseName, this.collectionName, recordId); - } - - // - // Lists all records in the database. - // - async listAll(max: number, next?: string): Promise> { - return await this.api.listAll(this.databaseName, this.collectionName, max, next); - } - - // - // Gets a page of records from the database. - // - async getAll(max: number, next?: string): Promise> { - return await this.api.getAll(this.databaseName, this.collectionName, max, next); - } - - // - // Deletes a database record. - // - async deleteOne(recordId: string): Promise { - await this.api.deleteOne(this.databaseName, this.collectionName, recordId); - } - - // - // Returns true if there are no records in the collection. - // - async none(): Promise { - throw new Error("Not implemented."); - } -} \ No newline at end of file diff --git a/packages/database/src/lib/cloud/cloud-database.ts b/packages/database/src/lib/cloud/cloud-database.ts deleted file mode 100644 index 08ab91e..0000000 --- a/packages/database/src/lib/cloud/cloud-database.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IApi } from "../api"; -import { IDatabase } from "../database"; -import { ICloudDatabaseCollection, CloudDatabaseCollection } from "./cloud-database-collection"; - -export interface ICloudDatabase extends IDatabase { -} - -// -// Implements a database. -// -export class CloudDatabase implements ICloudDatabase { - constructor(private databaseName: string, private api: IApi) { - } - - // - // Gets a database collection by name. - // - collection(collectionName: string): ICloudDatabaseCollection { - return new CloudDatabaseCollection(this.databaseName, collectionName, this.api); - } -} diff --git a/packages/database/src/lib/cloud/cloud-databases.ts b/packages/database/src/lib/cloud/cloud-databases.ts deleted file mode 100644 index 4d57da7..0000000 --- a/packages/database/src/lib/cloud/cloud-databases.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IApi } from "../api"; -import { IDatabases } from "../databases"; -import { ICloudDatabase, CloudDatabase } from "./cloud-database"; - -export interface ICloudDatabases extends IDatabases { - // - // Gets a database by name. - // - database(databaseName: string): ICloudDatabase; -} - -export class CloudDatabases implements ICloudDatabases { - - constructor(private api: IApi) { - } - - // - // Gets a database by name. - // - database(databaseName: string): ICloudDatabase { - return new CloudDatabase(databaseName, this.api); - } -} diff --git a/packages/database/src/lib/database-collection.ts b/packages/database/src/lib/database-collection.ts deleted file mode 100644 index c76cc39..0000000 --- a/packages/database/src/lib/database-collection.ts +++ /dev/null @@ -1,40 +0,0 @@ -// -// A page of records from the database. - -import { IPage } from "../defs/page"; - -// -// -// Implements a collection of records in the database. -// -export interface IDatabaseCollection { - // - // Sets a new record to the database. - // - setOne(id: string, record: RecordT): Promise; - - // - // Gets one record by id. - // - getOne(id: string): Promise; - - // - // Lists all records in the database. - // - listAll(max: number, next?: string): Promise>; - - // - // Gets a page of records from the database. - // - getAll(max: number, next?: string): Promise>; - - // - // Deletes a record from the database. - // - deleteOne(id: string): Promise; - - // - // Returns true if there are no records in the collection. - // - none(): Promise; -} diff --git a/packages/database/src/lib/database.ts b/packages/database/src/lib/database.ts deleted file mode 100644 index 2f3161f..0000000 --- a/packages/database/src/lib/database.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IDatabaseCollection } from "./database-collection"; - -// -// Implements a database. -// -export interface IDatabase { - // - // Gets a database collection by name. - // - collection(name: string): IDatabaseCollection; -} diff --git a/packages/database/src/lib/get-journal.ts b/packages/database/src/lib/get-journal.ts deleted file mode 100644 index 6f715cd..0000000 --- a/packages/database/src/lib/get-journal.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { IDatabaseOpRecord, IOpSelection } from "../defs/ops"; -import { IPage } from "../defs/page"; -import { binarySearch } from "./binary-search"; -import { IDatabase } from "./database"; - -// -// Records a database operation against a particular record. -// -export interface IDatabaseOpResult { - // - // The name of the database collection to which the operation is applied. - // - collectionName: string; - - // - // The id of the database record to which the operation is applied. - // - recordId: string; - - // - // The operation that was applied to the record. - // - op: IOpSelection; -} - -// -// The result of get the database journal. -// -export interface IJournalResult { - // - // Operations recorded against the collection. - // - ops: IDatabaseOpResult[]; - - // - // The id of the latest update that has been retreived. - // - latestUpdateId?: string; -} - -// -// Gets the journal of operations that have been applied to the database. -// -export async function getJournal(database: IDatabase, clientId: string, lastUpdateId?: string): Promise { - - let allRecords: IDatabaseOpRecord[] = []; - let done = false; - let latestUpdateId: string | undefined = undefined; - let next: string | undefined = undefined; - const journalCollection = database.collection("journal"); - - while (!done) { - const result: IPage = await journalCollection.listAll(1000, next); - next = result.next; - - if (result.next === undefined) { - // No more journal records to fetch. - done = true; - } - - let journalRecordIds = result.records; - if (latestUpdateId === undefined && journalRecordIds.length > 0) { - latestUpdateId = journalRecordIds[0]; - } - - // - // Only deliver updates that are newer than the record that was last seen. - // - if (lastUpdateId !== undefined) { - const cutOffIndex = binarySearch(journalRecordIds, lastUpdateId); - if (cutOffIndex !== undefined) { - journalRecordIds = journalRecordIds.slice(0, cutOffIndex); - done = true; // We found the requested update id, no need to continue searching through the journal. - } - } - - const journalRecordPromises = journalRecordIds.map(async id => { - const assetRecord = await journalCollection.getOne(id); - return assetRecord!; // These records should always exist, since we just looked them up. - }); - - let journalRecords = await Promise.all(journalRecordPromises); - - // Don't deliver updates that originated from the requesting client. - journalRecords = journalRecords.filter(journalRecord => journalRecord.clientId !== clientId); - allRecords = allRecords.concat(journalRecords); - } - - // - // Operations are pulled out in reverse chronological order, this puts them in chronological order. - // - allRecords.reverse(); - - return { - ops: allRecords.map(journalRecord => { - return { - collectionName: journalRecord.collectionName, - recordId: journalRecord.recordId, - op: journalRecord.op, - }; - }), - latestUpdateId, - }; -} \ No newline at end of file diff --git a/packages/database/src/lib/indexeddb/indexeddb-database-collection.ts b/packages/database/src/lib/indexeddb/indexeddb-database-collection.ts deleted file mode 100644 index 454b023..0000000 --- a/packages/database/src/lib/indexeddb/indexeddb-database-collection.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { IPage } from "../../defs/page"; -import { IDatabaseCollection } from "../database-collection"; -import { deleteRecord, getAllKeys, getAllRecords, getLeastRecentRecord, getNumRecords, getRecord, storeRecord } from "./indexeddb"; - -export interface IIndexeddbDatabaseCollection extends IDatabaseCollection { -} - -export class IndexeddbDatabaseCollection implements IIndexeddbDatabaseCollection { - - constructor(private collectionName: string, private openDb: () => Promise) { - } - - // - // Sets a new record to the database. - // - async setOne(id: string, record: RecordT): Promise { - const db = await this.openDb(); - await storeRecord(db, this.collectionName, id, record); - } - - // - // Gets one record by id. - // - async getOne(id: string): Promise { - const db = await this.openDb(); - return await getRecord(db, this.collectionName, id); - } - - // - // Lists all records in the database. - // - async listAll(max: number, next?: string): Promise> { - const db = await this.openDb(); - return { - records: await getAllKeys(db, this.collectionName), - next: undefined, // Indexeddb doesn't support pagination. - }; - } - - // - // Gets a page of records from the database. - // - async getAll(max: number, next?: string): Promise> { - const db = await this.openDb(); - return { - records: await getAllRecords(db, this.collectionName), - next: undefined, // Indexeddb doesn't support pagination. - }; - } - - // - // Deletes a database record. - // - async deleteOne(recordId: string): Promise { - const db = await this.openDb(); - await deleteRecord(db, this.collectionName, recordId); - } - - // - // Returns true if there are no records in the collection. - // - async none(): Promise { - const db = await this.openDb(); - const numRecords = await getNumRecords(db, this.collectionName); - return numRecords === 0; - } -} \ No newline at end of file diff --git a/packages/database/src/lib/storage/storage-directory.ts b/packages/database/src/lib/storage/storage-directory.ts deleted file mode 100644 index 01ed842..0000000 --- a/packages/database/src/lib/storage/storage-directory.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Readable } from "stream"; -import { IFileInfo, IListResult, IStorage } from "./storage"; - -// -// Represents a nested directory in the storage system. -// -export class StorageDirectory implements IStorage { - - constructor(private storage: IStorage, private path: string) { - } - - // - // List files in storage. - // - list(path: string, max: number, next?: string): Promise { - return this.storage.list(`${this.path}/${path}`, max, next); - } - - // - // Returns true if the specified file exists. - // - exists(path: string, fileName: string): Promise { - return this.storage.exists(`${this.path}/${path}`, fileName); - } - - // - // Gets info about a file. - // - info(path: string, fileName: string): Promise { - return this.storage.info(`${this.path}/${path}`, fileName); - } - - // - // Reads a file from storage. - // Returns undefined if the file doesn't exist. - // - read(path: string, fileName: string): Promise { - return this.storage.read(`${this.path}/${path}`, fileName); - } - - // - // Writes a file to storage. - // - write(path: string, fileName: string, contentType: string, data: Buffer): Promise { - return this.storage.write(`${this.path}/${path}`, fileName, contentType, data); - } - - // - // Streams a file from stroage. - // - readStream(path: string, fileName: string): Readable { - return this.storage.readStream(`${this.path}/${path}`, fileName); - } - - // - // Writes an input stream to storage. - // - writeStream(path: string, fileName: string, contentType: string, inputStream: Readable): Promise { - return this.storage.writeStream(`${this.path}/${path}`, fileName, contentType, inputStream); - } - - // - // Deletes the file from storage. - // - delete(path: string, fileName: string): Promise { - return this.storage.delete(`${this.path}/${path}`, fileName); - } -} \ No newline at end of file diff --git a/packages/database/src/lib/storagedb/storage-database-collection.ts b/packages/database/src/lib/storagedb/storage-database-collection.ts deleted file mode 100644 index 7dd9362..0000000 --- a/packages/database/src/lib/storagedb/storage-database-collection.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { IPage } from "../../defs/page"; -import { IDatabaseCollection } from "../database-collection"; -import { IStorage } from "../storage/storage"; - -// -// Read and write the database to storage. -// -export class StorageDatabaseCollection implements IDatabaseCollection { - - constructor(private storage: IStorage, private path: string) { - } - - // - // Sets a new record to the database. - // - async setOne(id: string, record: RecordT): Promise { - await this.storage.write(this.path, id, "application/json", Buffer.from(JSON.stringify(record))); - } - - // - // Gets one record by id. - // - async getOne(id: string): Promise { - const buffer = await this.storage.read(this.path, id); - if (!buffer) { - return undefined; - } - - return JSON.parse(buffer.toString('utf-8')); - } - - // - // Lists all records in the database. - // - async listAll(max: number, next?: string): Promise> { - const listResult = await this.storage.list(this.path, max, next); - return { - records: listResult.fileNames, - next: listResult.next, - }; - } - - // - // Gets a page of records from the database. - // - async getAll(max: number, next?: string): Promise> { - const listResult = await this.storage.list(this.path, max, next); - const records: RecordT[] = []; - for (const fileName of listResult.fileNames) { - records.push((await this.getOne(fileName))!); - } - - return { - records, - next: listResult.next, - }; - } - - // - // Deletes a record from the database. - // - async deleteOne(id: string): Promise { - await this.storage.delete(this.path, id); - } - - // - // Returns true if there are no records in the collection. - // - async none(): Promise { - const result = await this.getAll(1, undefined); - return result.records.length === 0; - } -} \ No newline at end of file diff --git a/packages/database/src/lib/storagedb/storage-database.ts b/packages/database/src/lib/storagedb/storage-database.ts deleted file mode 100644 index 807ad98..0000000 --- a/packages/database/src/lib/storagedb/storage-database.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { StorageDatabaseCollection } from "./storage-database-collection"; -import { IStorage } from "../storage/storage"; -import { IDatabase } from "../database"; -import { IDatabaseCollection } from "../database-collection"; -import { StorageDirectory } from "../storage/storage-directory"; - -// -// Implements a database on file storage. -// -export class StorageDatabase implements IDatabase { - - private storage: IStorage; - - constructor(storage: IStorage, path?: string) { - if (path) { - this.storage = new StorageDirectory(storage, path); - } - else { - this.storage = storage; - } - } - - // - // Gets a database collection by name. - // - collection(collectionName: string): IDatabaseCollection { - return new StorageDatabaseCollection(this.storage, collectionName); - } -} \ No newline at end of file diff --git a/packages/database/src/lib/storagedb/storage-databases.ts b/packages/database/src/lib/storagedb/storage-databases.ts deleted file mode 100644 index 07429fd..0000000 --- a/packages/database/src/lib/storagedb/storage-databases.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IDatabase } from "../database"; -import { IDatabases } from "../databases"; -import { IStorage } from "../storage/storage"; -import { StorageDatabase } from "./storage-database"; - - -export class StorageDatabases implements IDatabases { - - constructor(private storage: IStorage) { - } - - // - // Gets a database by name. - // - database(databaseName: string): IDatabase { - return new StorageDatabase(this.storage, databaseName); - } -} diff --git a/packages/database/src/lib/sync/sync-incoming.ts b/packages/database/src/lib/sync/sync-incoming.ts deleted file mode 100644 index 4b2b942..0000000 --- a/packages/database/src/lib/sync/sync-incoming.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { IApi } from "../api"; -import { IAssetSink } from "../asset-sink"; -import { IDatabase } from "../database"; -import { IUpdateIdRecord } from "./update-id-record"; - -interface IProps { - // - // Collections for which to receive updates. - // - collectionIds: string[]; - - // - // The interface to the backend. - // - api: IApi; - - // - // The local user database. - // - userDatabase: IDatabase; - - // - // Interface to local indexeddb databases. - // - indexeddbSink: IAssetSink; -} - -// -// Receive incoming asset updates from the cloud. -// -export async function syncIncoming({ collectionIds, api, userDatabase, indexeddbSink }: IProps): Promise { - // - // Retreive updates for the collections we have access to, but only - // from the latest update that was received. - // - for (const collectionId of collectionIds) { - const lastUpdateIdCollection = userDatabase.collection("last-update-id"); - const lastUpdateIdRecord = await lastUpdateIdCollection.getOne(collectionId); - const journalResult = await api.getJournal(collectionId, lastUpdateIdRecord?.lastUpdateId); - - if (journalResult.ops.length === 0) { - // Nothing to do. - break; - } - - // - // Apply incoming changes to the local database. - // - indexeddbSink.submitOperations(journalResult.ops.map(journalRecord => ({ - databaseName: collectionId, - collectionName: journalRecord.collectionName, - recordId: journalRecord.recordId, - op: journalRecord.op, - }))); - - if (journalResult.latestUpdateId !== undefined) { - // - // Record the latest update that was received. - // - await lastUpdateIdCollection.setOne(collectionId, { - _id: collectionId, - lastUpdateId: journalResult.latestUpdateId - }); - } - - console.log(`Processed incoming updates for ${collectionId}: ${journalResult.ops.length} ops`); - } -} diff --git a/packages/database/src/lib/sync/sync-initial.ts b/packages/database/src/lib/sync/sync-initial.ts deleted file mode 100644 index fbe297c..0000000 --- a/packages/database/src/lib/sync/sync-initial.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { IAsset } from "../../defs/asset"; -import { IApi } from "../api"; -import { IAssetSink } from "../asset-sink"; -import { IAssetSource } from "../asset-source"; -import { IDatabases } from "../databases"; -import { IIndexeddbDatabases } from "../indexeddb/indexeddb-databases"; -import { visitRecords } from "../visit-records"; - -interface IProps { - // - // Collections to synchronize. - // - collectionIds: string[]; - - // - // The interface to the backend. - // - api: IApi; - - // - // Local databases to poplulate. - // - indexeddbDatabases: IIndexeddbDatabases; - - // - // Interface to cloud databases. - // - cloudDatabases: IDatabases; - - // - // The cloud source of assets. - // - cloudSource: IAssetSource; - - // - // The local source of assets. - // - indexeddbSource: IAssetSource; - - // - // The local sink for assets. - // - indexeddbSink: IAssetSink; -} - -// -// Perform the initial database synchronization. -// -export async function initialSync({ collectionIds, api, indexeddbDatabases, cloudDatabases, cloudSource, indexeddbSource, indexeddbSink }: IProps): Promise { - for (const collectionId of collectionIds) { - // - // Records the latest update id for the collection. - // This should be done before the initial sync to avoid missing updates. - // - const latestUpdateId = await api.getLatestUpdateId(collectionId); - if (latestUpdateId !== undefined) { - // - // Record the latest update that was received. - // - const userDatabase = indexeddbDatabases.database("user"); - userDatabase.collection("last-update-id").setOne(collectionId, { lastUpdateId: latestUpdateId }); - } - - const cloudAssetDatabase = cloudDatabases.database(collectionId); - const localAssetDatabase = indexeddbDatabases.database(collectionId); - for (const collectionName of ["metadata", "hashes"]) { - const localCollection = localAssetDatabase.collection(collectionName); - const noRecords = await localCollection.none(); - if (noRecords) { - // - // Assume that no records locally means we need to get all records down for this collection. - // - await visitRecords(cloudAssetDatabase, collectionName, async (id, record) => { - await localCollection.setOne(id, record); // Store it locally. - }); - } - } - - // - // Pre-cache all thumbnails. - // - // await visitRecords(cloudAssetDatabase, "metadata", async (id, record) => { - // await cacheThumbnail(collectionId, id, indexeddbSource, indexeddbSink, cloudSource); - // }); - } -} - -// -// Pre-caches a thumbnail. -// -// async function cacheThumbnail(collectionId: string, assetId: string, indexeddbSource: IAssetSource, indexeddbSink: IAssetSink, cloudSource: IAssetSource) { -// const localThumbData = await indexeddbSource.loadAsset(collectionId, assetId, "thumb"); -// if (localThumbData === undefined) { -// const assetData = await cloudSource.loadAsset(collectionId, assetId, "thumb"); -// if (assetData) { -// await indexeddbSink.storeAsset(collectionId, assetId, "thumb", assetData); -// // console.log(`Cached thumbnail for ${collectionId}/${assetId}`); -// } -// } -// else { -// // console.log(`Thumbnail for ${collectionId}/${assetId} already cached`); -// } -// } \ No newline at end of file diff --git a/packages/database/src/lib/sync/update-id-record.ts b/packages/database/src/lib/sync/update-id-record.ts deleted file mode 100644 index 47cfccd..0000000 --- a/packages/database/src/lib/sync/update-id-record.ts +++ /dev/null @@ -1,14 +0,0 @@ -// -// Records last update ids for each collection in the local database. -// -export interface IUpdateIdRecord { - // - // The ID of the record. - // - _id: string; - - // - // The last update id for the collection. - // - lastUpdateId: string; -} \ No newline at end of file diff --git a/packages/database/src/lib/visit-records.ts b/packages/database/src/lib/visit-records.ts deleted file mode 100644 index 890975e..0000000 --- a/packages/database/src/lib/visit-records.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { IPage } from "../defs/page"; -import { IDatabase } from "./database"; - -export type CallbackFn = (id: string, record: RecordT) => Promise; - -// -// Invoke a callback function for every record in the requested collection. -// -export async function visitRecords(database: IDatabase, collectionName: string, callback: CallbackFn): Promise { - const collection = database.collection(collectionName); - let next: string | undefined = undefined; - while (true) { - const page: IPage = await collection.listAll(1000, next); - next = page.next; - - for (const recordId of page.records) { - const record = await collection.getOne(recordId); - if (record !== undefined) { - callback(recordId, record); - } - } - - if (next === undefined) { - break; - } - } -} \ No newline at end of file diff --git a/packages/database/src/tests/lib/get-journal.test.ts b/packages/database/src/tests/lib/get-journal.test.ts deleted file mode 100644 index 7bb87d9..0000000 --- a/packages/database/src/tests/lib/get-journal.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { getJournal } from "../../lib/get-journal"; - -describe("get journal", () => { - - test("can get empty journal", async () => { - const mockJournalCollection: any = { - listAll: async () => { - return { - records: [], - next: undefined, - }; - }, - }; - const mockDatabase: any = { - collection: () => mockJournalCollection, - }; - const clientId = "client1"; - const lastUpdateId = undefined; - - - const result = await getJournal(mockDatabase, clientId, lastUpdateId); - expect(result.ops).toEqual([]); - expect(result.latestUpdateId).toBeUndefined(); - }); - - test("can get journal with records", async () => { - const records = [ - { - clientId: "client-2", - recordId: "05", - collectionName: "collection-1", - op: {}, - }, - { - clientId: "client-2", - recordId: "06", - collectionName: "collection-1", - op: {}, - }, - ] - const mockJournalCollection: any = { - getOne: async (id: string) => { - return records.find(r => r.recordId === id); - }, - listAll: async () => { - return { - records: records.map(r => r.recordId), - next: undefined, - }; - }, - }; - const mockDatabase: any = { - collection: () => mockJournalCollection, - }; - - const result = await getJournal(mockDatabase, "client-1", undefined); - expect(result.ops).toEqual([ - { - collectionName: "collection-1", - recordId: "06", - op: {}, - }, - { - collectionName: "collection-1", - recordId: "05", - op: {}, - }, - ]); - expect(result.latestUpdateId).toBe("05"); - }); - - test("can get journal with multiple pages", async () => { - const record1 = { - clientId: "client-2", - recordId: "05", - collectionName: "collection-1", - op: {}, - }; - const record2 = { - clientId: "client-2", - recordId: "06", - collectionName: "collection-1", - op: {}, - }; - const mockJournalCollection: any = { - getOne: async (id: string) => { - if (id === "05") { - return record1; - } - else if (id === "06") { - return record2; - } - - return undefined; - }, - listAll: jest.fn() - .mockImplementationOnce(async () => ({ - records: [ record1.recordId ], - next: "next-page", - })) - .mockImplementationOnce(async () => ({ - records: [ record2.recordId ], - next: undefined, - })), - }; - const mockDatabase: any = { - collection: () => mockJournalCollection, - }; - - const result = await getJournal(mockDatabase, "client-1", undefined); - expect(result.ops).toEqual([ - { - collectionName: "collection-1", - recordId: "06", - op: {}, - }, - { - collectionName: "collection-1", - recordId: "05", - op: {}, - }, - ]); - expect(result.latestUpdateId).toBe("05"); - }); - - test("records from requesting client are filtered out", async () => { - const records = [ - { - clientId: "client-2", - recordId: "02", - collectionName: "collection-1", - op: {}, - }, - { - clientId: "client-1", - recordId: "07", - collectionName: "collection-1", - op: {}, - }, - { - clientId: "client-3", - recordId: "08", - collectionName: "collection-1", - op: {}, - }, - ] - const mockJournalCollection: any = { - getOne: async (id: string) => { - return records.find(r => r.recordId === id); - }, - listAll: async () => { - return { - records: records.map(r => r.recordId), - next: undefined, - }; - }, - }; - const mockDatabase: any = { - collection: () => mockJournalCollection, - }; - - const result = await getJournal(mockDatabase, "client-1", undefined); - expect(result.ops).toEqual([ - { - collectionName: "collection-1", - recordId: "08", - op: {}, - }, - { - collectionName: "collection-1", - recordId: "02", - op: {}, - }, - ]); - expect(result.latestUpdateId).toBe("02"); - }); -}); \ No newline at end of file diff --git a/packages/database/src/tests/lib/visit-records.test.ts b/packages/database/src/tests/lib/visit-records.test.ts deleted file mode 100644 index 7a87298..0000000 --- a/packages/database/src/tests/lib/visit-records.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { visitRecords } from "../../lib/visit-records"; - -describe("visit records", () => { - - test("can visit zero records", async () => { - const mockCollection: any = { - listAll: async () => ({ records: [], next: undefined }), - }; - const mockDatabase: any = { - collection: jest.fn(() => mockCollection), - }; - - const callback = jest.fn(); - - await visitRecords(mockDatabase, "ABC", callback); - - expect(callback).toHaveBeenCalledTimes(0); - }); - - test("can visit multiple records", async () => { - const mockCollection: any = { - listAll: async () => ({ records: ["1", "2"], next: undefined }), - getOne: async (recordId: string) => ({ id: recordId }), - }; - const mockDatabase: any = { - collection: jest.fn(() => mockCollection), - }; - - const callback = jest.fn(); - - await visitRecords(mockDatabase, "ABC", callback); - - expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith(1, "1", { id: "1" }); - expect(callback).toHaveBeenNthCalledWith(2, "2", { id: "2" }); - }); - - test("can visit multiple pages of recoreds", async () => { - const mockCollection: any = { - listAll: jest.fn() - .mockReturnValueOnce({ records: ["1", "2"], next: "next" }) - .mockReturnValueOnce({ records: ["3"], next: undefined }), - getOne: async (recordId: string) => ({ id: recordId }), - }; - const mockDatabase: any = { - collection: jest.fn(() => mockCollection), - }; - - const callback = jest.fn(); - - await visitRecords(mockDatabase, "ABC", callback); - - expect(callback).toHaveBeenCalledTimes(3); - expect(callback).toHaveBeenNthCalledWith(1, "1", { id: "1" }); - expect(callback).toHaveBeenNthCalledWith(2, "2", { id: "2" }); - expect(callback).toHaveBeenNthCalledWith(3, "3", { id: "3" }); - }); -}); \ No newline at end of file diff --git a/packages/database/README.md b/packages/defs/README.md similarity index 100% rename from packages/database/README.md rename to packages/defs/README.md diff --git a/packages/database/jest.config.js b/packages/defs/jest.config.js similarity index 100% rename from packages/database/jest.config.js rename to packages/defs/jest.config.js diff --git a/packages/database/package.json b/packages/defs/package.json similarity index 58% rename from packages/database/package.json rename to packages/defs/package.json index 6510871..17f1410 100644 --- a/packages/database/package.json +++ b/packages/defs/package.json @@ -1,13 +1,9 @@ { - "name": "database", + "name": "defs", "version": "1.0.0", - "description": "Database library for Photosphere.", + "description": "Shared definitions between backend and frontend.", "main": "build/index.js", "scripts": { - "t": "pnpm run test", - "tw": "pnpm run test:watch", - "test": "jest --coverage", - "test:watch": "jest --watch", "c": "pnpm run compile", "cw": "pnpm run compile:watch", "compile": "tsc", @@ -18,13 +14,9 @@ "author": "ashley@codecapers.com.au", "license": "MIT", "devDependencies": { - "@types/jest": "^29.4.0", "@types/node": "^20.3.1", - "jest": "^29.0.1", - "ts-jest": "^29.0.5", "typescript": "^4.9.4" }, "dependencies": { - "dayjs": "^1.11.7" } } diff --git a/packages/database/r.bat b/packages/defs/r.bat similarity index 100% rename from packages/database/r.bat rename to packages/defs/r.bat diff --git a/packages/defs/src/index.ts b/packages/defs/src/index.ts new file mode 100644 index 0000000..7545303 --- /dev/null +++ b/packages/defs/src/index.ts @@ -0,0 +1,4 @@ +export * from "./lib/op"; +export * from "./lib/database-op"; +export * from "./lib/database-op-record"; +export * from "./lib/asset"; \ No newline at end of file diff --git a/packages/database/src/defs/asset.ts b/packages/defs/src/lib/asset.ts similarity index 94% rename from packages/database/src/defs/asset.ts rename to packages/defs/src/lib/asset.ts index f54225e..6bdc656 100644 --- a/packages/database/src/defs/asset.ts +++ b/packages/defs/src/lib/asset.ts @@ -1,8 +1,6 @@ // // Represents an asset that has been uploaded to the backend. // -// TODO: Share this code with the backend. -// // // Full asset data. @@ -14,12 +12,16 @@ export interface IAsset { // _id: string; + // + // The ID of the set that contains the asset. + // + setId: string; + // // The original name of the asset before it was uploaded. // origFileName: string; - // // The original directory of the asset before it was uploaded. // diff --git a/packages/defs/src/lib/database-op-record.ts b/packages/defs/src/lib/database-op-record.ts new file mode 100644 index 0000000..a0375de --- /dev/null +++ b/packages/defs/src/lib/database-op-record.ts @@ -0,0 +1,42 @@ +// +// Records an operation against a particular database record. + +import { IOpSelection } from "./op"; + +// +export interface IDatabaseOpRecord { + // + // The ID of the database op record. + // + _id: string; + + // + // The date the server received the operation. + // + serverTime: Date; + + // + // Records the sequence in which the operation were received. + // + sequence: number; + + // + // The client where the operation originated. + // + clientId: string; + + // + // The name of the database collection to which the operation is applied. + // + collectionName: string; + + // + // The id of the database record to which the operation is applied. + // + recordId: string; + + // + // The operation that was applied to the asset. + // + op: IOpSelection; +} diff --git a/packages/defs/src/lib/database-op.ts b/packages/defs/src/lib/database-op.ts new file mode 100644 index 0000000..b91d561 --- /dev/null +++ b/packages/defs/src/lib/database-op.ts @@ -0,0 +1,23 @@ +import { IOpSelection } from "./op"; + +export interface IDatabaseOp { + // + // The name of the database collection to which the operation is applied. + // + collectionName: string; + + // + // The set to apply the operation to. + // + setId: string; + + // + // The id of the asset to which operations are applied. + // + recordId: string; + + // + // The operation to apply to the asset. + // + op: IOpSelection; +} \ No newline at end of file diff --git a/packages/database/src/defs/ops.ts b/packages/defs/src/lib/op.ts similarity index 50% rename from packages/database/src/defs/ops.ts rename to packages/defs/src/lib/op.ts index e933282..fd810e6 100644 --- a/packages/database/src/defs/ops.ts +++ b/packages/defs/src/lib/op.ts @@ -71,56 +71,4 @@ export interface IPullOp extends IOp { // // Specifies the range of possible operations. // -export type IOpSelection = ISetOp | IPushOp | IPullOp; - -export interface IDatabaseOp { - // - // The name of the database to which this operation is applied. - // - databaseName: string; - - // - // The name of the database collection to which the operation is applied. - // - collectionName: string; - - // - // The id of the asset to which operations are applied. - // - recordId: string; - - // - // The operation to apply to the asset. - // - op: IOpSelection; -} - -// -// Records an operation against a particular database record. -// -export interface IDatabaseOpRecord { - // - // The date the server received the operation. - // - serverTime: string; - - // - // The client where the operation originated. - // - clientId: string; - - // - // The name of the database collection to which the operation is applied. - // - collectionName: string; - - // - // The id of the database record to which the operation is applied. - // - recordId: string; - - // - // The operation that was applied to the asset. - // - op: IOpSelection; -} +export type IOpSelection = ISetOp | IPushOp | IPullOp; \ No newline at end of file diff --git a/packages/database/tsconfig.json b/packages/defs/tsconfig.json similarity index 100% rename from packages/database/tsconfig.json rename to packages/defs/tsconfig.json diff --git a/packages/user-interface/package.json b/packages/user-interface/package.json index aa93514..befc0e2 100644 --- a/packages/user-interface/package.json +++ b/packages/user-interface/package.json @@ -46,6 +46,6 @@ "react-dom": "^18.2.0", "react-fps-stats": "^0.3.1", "react-router-dom": "^6.4.1", - "database": "workspace:*" + "defs": "workspace:*" } } diff --git a/packages/user-interface/src/components/gallery.tsx b/packages/user-interface/src/components/gallery.tsx index a9b869a..a36b44e 100644 --- a/packages/user-interface/src/components/gallery.tsx +++ b/packages/user-interface/src/components/gallery.tsx @@ -1,5 +1,4 @@ import React, { useRef, useState } from "react"; -import { IGalleryItem, ISelectedGalleryItem } from "../lib/gallery-item"; import { GalleryLayout } from "./gallery-layout"; import useResizeObserver from "@react-hook/resize-observer"; import { useGallery } from "../context/gallery-context"; diff --git a/packages/user-interface/src/context/api-context.tsx b/packages/user-interface/src/context/api-context.tsx index e9937ca..bdd9bbc 100644 --- a/packages/user-interface/src/context/api-context.tsx +++ b/packages/user-interface/src/context/api-context.tsx @@ -2,8 +2,11 @@ import React, { createContext, ReactNode, useContext, useEffect, useState } from import axios from "axios"; import { useAuth } from "./auth-context"; import { useClientId } from "../lib/use-client-id"; -import { IAsset, IAssetData, IDatabaseOp, IJournalResult, IOpSelection, IUser, IApi, IGetAssetsResult } from "database"; -import { IPage } from "database/build/defs/page"; +import { IApi, IJournalResult } from "../lib/api"; +import { IUser } from "../def/user"; +import { IAssetData } from "../def/asset-data"; +import { IDatabaseOp } from "defs"; +import { IRecord } from "../lib/database-collection"; const BASE_URL = process.env.BASE_URL as string; if (!BASE_URL) { @@ -61,31 +64,30 @@ export function ApiContextProvider({ children }: IProps) { } // - // Retreives the latest update id for a collection. + // Retreives the latest server time. // - async function getLatestUpdateId(collectionId: string): Promise { + async function getLatestTime(): Promise { await loadToken(); const token = getToken(); - const url = `${BASE_URL}/latest-update-id`; + const url = `${BASE_URL}/latest-time`; const response = await axios.get( url, { headers: { - col: collectionId, Authorization: `Bearer ${token}`, Accept: "application/json", }, } ); - return response.data.latestUpdateId; + return response.data.latestTime; } // // Retreives the data for an asset from the backend. // - async function getAsset(collectionId: string, assetId: string, assetType: string): Promise { - const url = `${BASE_URL}/asset?id=${assetId}&type=${assetType}&col=${collectionId}`; //todo: Some of these parameters might be better as headers. + async function getAsset(setId: string, assetId: string, assetType: string): Promise { + const url = `${BASE_URL}/asset?id=${assetId}&type=${assetType}&set=${setId}`; await loadToken(); const token = getToken(); const response = await axios.get(url, { @@ -102,7 +104,7 @@ export function ApiContextProvider({ children }: IProps) { // // Uploads an asset to the backend. // - async function uploadSingleAsset(collectionId: string, assetId: string, assetType: string, assetData: IAssetData): Promise { + async function uploadSingleAsset(setId: string, assetId: string, assetType: string, assetData: IAssetData): Promise { await loadToken(); const token = getToken(); @@ -112,7 +114,7 @@ export function ApiContextProvider({ children }: IProps) { { headers: { "content-type": assetData.contentType, - col: collectionId, + set: setId, id: assetId, "asset-type": assetType, Authorization: `Bearer ${token}`, @@ -152,7 +154,7 @@ export function ApiContextProvider({ children }: IProps) { // // Gets the journal of operations that have been applied to the database. // - async function getJournal(collectionId: string, lastUpdateId?: string): Promise { + async function getJournal(lastUpdateTime?: string): Promise { if (!clientId) { throw new Error(`Client id not set.`); @@ -165,8 +167,7 @@ export function ApiContextProvider({ children }: IProps) { const response = await axios.post( url, { - collectionId, - lastUpdateId, + lastUpdateTime, clientId, }, { @@ -180,62 +181,13 @@ export function ApiContextProvider({ children }: IProps) { return response.data; } - // - // Sets a new record to the database. - // - async function setOne(databaseName: string, collectionName: string, recordId: string, record: any): Promise { - await loadToken(); - const token = getToken(); - - const url = `${BASE_URL}/set-one`; - const response = await axios.post( - url, - { - databaseName, - collectionName, - recordId, - record, - }, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: "application/json", - }, - }, - ); - - return response.data; - } - // // Gets one record by id. // - async function getOne(databaseName: string, collectionName: string, recordId: string): Promise { + async function getOne(collectionName: string, recordId: string): Promise { await loadToken(); const token = getToken(); - const url = `${BASE_URL}/get-one?db=${databaseName}&col=${collectionName}&id=${recordId}`; - const response = await axios.get( - url, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: "application/json", - }, - } - ); - return response.data; - } - - // - // Lists all records in the database. - // - async function listAll(databaseName: string, collectionName: string, max: number, next?: string): Promise> { - await loadToken(); - const token = getToken(); - let url = `${BASE_URL}/list-all?db=${databaseName}&col=${collectionName}&max=${max}`; - if (next) { - url += `&next=${next}`; - } + const url = `${BASE_URL}/get-one?col=${collectionName}&id=${recordId}`; const response = await axios.get( url, { @@ -251,13 +203,10 @@ export function ApiContextProvider({ children }: IProps) { // // Gets a page of records from the database. // - async function getAll(databaseName: string, collectionName: string, max: number, next?: string): Promise> { + async function getAll(setId: string, collectionName: string, skip: number, limit: number): Promise { await loadToken(); const token = getToken(); - let url = `${BASE_URL}/get-all?db=${databaseName}&col=${collectionName}&max=${max}`; - if (next) { - url += `&next=${next}`; - } + const url = `${BASE_URL}/get-all?set=${setId}&col=${collectionName}&skip=${skip}&limit=${limit}`; const response = await axios.get( url, { @@ -270,42 +219,16 @@ export function ApiContextProvider({ children }: IProps) { return response.data; } - // - // Deletes a database record. - // - async function deleteOne(databaseName: string, collectionName: string, recordId: string): Promise { - await loadToken(); - const token = getToken(); - const url = `${BASE_URL}/delete-one`; - const response = await axios.post( - url, - { - databaseName, - collectionName, - recordId, - }, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: "application/json", - }, - } - ); - } - const value: IApi = { isInitialised, getUser, - getLatestUpdateId, + getLatestTime, getAsset, uploadSingleAsset, submitOperations, getJournal, - setOne, getOne, - listAll, getAll, - deleteOne, }; return ( diff --git a/packages/user-interface/src/context/database-sync.tsx b/packages/user-interface/src/context/database-sync.tsx index 75f5098..4a8e68a 100644 --- a/packages/user-interface/src/context/database-sync.tsx +++ b/packages/user-interface/src/context/database-sync.tsx @@ -2,7 +2,15 @@ import React, { ReactNode, createContext, useContext, useEffect, useRef, useStat import { useOnline } from "../lib/use-online"; import { useIndexeddb } from "./indexeddb-context"; import { useApi } from "./api-context"; -import { IAsset, IAssetUpdateRecord, IAssetUploadRecord, IAssetSink, IAssetSource, IPersistentQueue, syncIncoming, syncOutgoing, initialSync, IDatabases, IIndexeddbDatabases, IUser } from "database"; +import { IDatabases } from "../lib/databases"; +import { IIndexeddbDatabases } from "../lib/indexeddb/indexeddb-databases"; +import { IAssetUpdateRecord } from "../lib/sync/asset-update-record"; +import { IAssetUploadRecord } from "../lib/sync/asset-upload-record"; +import { IPersistentQueue } from "../lib/sync/persistent-queue"; +import { syncIncoming } from "../lib/sync/sync-incoming"; +import { syncOutgoing } from "../lib/sync/sync-outgoing"; +import { IUser } from "../def/user"; +import { initialSync } from "../lib/sync/sync-initial"; const SYNC_POLL_PERIOD = 1000; @@ -22,22 +30,11 @@ const DbSyncContext = createContext(undefined); export interface IProps { - // - // Interface to the database in the cloud. - // - cloudDatabases: IDatabases; - // // Interface to the local indexeddb databases. // indexeddbDatabases: IIndexeddbDatabases; - cloudSource: IAssetSource; - cloudSink: IAssetSink; - indexeddbSource: IAssetSource; - indexeddbSink: IAssetSink; - localSource: IAssetSource; - // // Queues outgoing asset uploads. // @@ -51,7 +48,7 @@ export interface IProps { children: ReactNode | ReactNode[]; } -export function DbSyncContextProvider({ cloudDatabases, cloudSource, cloudSink, indexeddbDatabases, indexeddbSource, indexeddbSink, localSource, outgoingAssetUploadQueue, outgoingAssetUpdateQueue, children }: IProps) { +export function DbSyncContextProvider({ outgoingAssetUploadQueue, outgoingAssetUpdateQueue, indexeddbDatabases, children }: IProps) { const { isOnline } = useOnline(); const indexeddb = useIndexeddb(); @@ -114,7 +111,6 @@ export function DbSyncContextProvider({ cloudDatabases, cloudSource, cloudSink, console.error(err) }); }, [api.isInitialised, isOnline]); - useEffect(() => { if (initialSyncStarted.current) { @@ -133,12 +129,8 @@ export function DbSyncContextProvider({ cloudDatabases, cloudSource, cloudSink, try { console.log(`Doing initial sync...`); - // - // Collate the last update ids for each collection. - // - const collectionIds = user!.collections.access; - - await initialSync({ collectionIds, api, cloudDatabases, cloudSource, indexeddbDatabases, indexeddbSource, indexeddbSink }); + const setIds = user!.sets.access; + await initialSync({ setIds, api, indexeddbDatabases }); } catch (err) { console.error(`Initial sync failed:`); @@ -162,7 +154,6 @@ export function DbSyncContextProvider({ cloudDatabases, cloudSource, cloudSink, }, [isOnline, user]); useEffect(() => { - if (periodicSyncStart.current) { console.log(`Periodic sync already started.`); return; @@ -188,9 +179,9 @@ export function DbSyncContextProvider({ cloudDatabases, cloudSource, cloudSink, try { await syncOutgoing({ - cloudSink, outgoingAssetUploadQueue, outgoingAssetUpdateQueue, + api, }); } catch (err) { @@ -202,12 +193,11 @@ export function DbSyncContextProvider({ cloudDatabases, cloudSource, cloudSink, // // Collate the last update ids for each collection. // - const collectionIds = user!.collections.access; - const userDatabase = indexeddb.databases.database("user"); - await syncIncoming({ collectionIds, api, userDatabase, indexeddbSink }); + const setIds = user!.sets.access; + await syncIncoming({ setIds, indexeddbDatabases, api }); } catch (err) { - console.error(`Outgoing sync failed:`); + console.error(`Incoming sync failed:`); console.error(err); } diff --git a/packages/user-interface/src/context/gallery-context.tsx b/packages/user-interface/src/context/gallery-context.tsx index 3c46089..6a795e9 100644 --- a/packages/user-interface/src/context/gallery-context.tsx +++ b/packages/user-interface/src/context/gallery-context.tsx @@ -3,13 +3,16 @@ import { IGalleryItem, ISelectedGalleryItem } from "../lib/gallery-item"; import dayjs from "dayjs"; import { useDatabaseSync } from "./database-sync"; import flexsearch from "flexsearch"; -import { IAsset, IAssetSink, IAssetSource, IDatabaseOp, IPage } from "database"; -import { sleep } from "../lib/sleep"; +import { IAsset } from "defs"; +import { IAssetSource } from "../lib/asset-source"; +import { IAssetSink } from "../lib/asset-sink"; +import { IDatabaseOp } from "defs"; +import { uuid } from "../lib/uuid"; // -// Gets the sorting value from the asset. +// Gets the sorting value from the gallery item. // -export type SortFn = (asset: IAsset) => any; +export type SortFn = (galleryItem: IGalleryItem) => any; // // Gets the grouping value from the asset. @@ -136,7 +139,7 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // // The collection currently being viewed. // - const [ collectionId, setCollectionId ] = useState(undefined); + const [ setId, setSetid ] = useState(undefined); // // Asset that have been loaded from storage. @@ -206,50 +209,37 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // Loads assets on mount. // useEffect(() => { - if (user && isInitialized && source.isInitialised) { + if (user && isInitialized) { loadAssets(); } - }, [isInitialized, source.isInitialised, user]); + }, [isInitialized, user]); // // Loads assets into the gallery. // async function loadAssets(): Promise { - let _collectionId = collectionId; + let _collectionId = setId; if (_collectionId === undefined) { if (user === undefined) { throw new Error(`Expected to know the user when loading assets.`); } - _collectionId = user.collections.default; - setCollectionId(_collectionId); + _collectionId = user.sets.default; + setSetid(_collectionId); } + const assets = await source.loadAssets(_collectionId!); const galleryItems: IGalleryItem[] = []; - let next: string | undefined = undefined; - while (true) { - const page: IPage = await source.loadAssets(_collectionId, 1000, next); - next = page.next; - for (const asset of page.records) { - const item = assetToGalleryItem(asset); - loadedAssets.current.set(item._id, item); - searchIndexRef.current.add(item._id, item); - galleryItems.push(item); - } - // Renders the assets that we know about already. - setAssets(applySort(galleryItems)); + for (const asset of assets) { + const item = assetToGalleryItem(asset); + loadedAssets.current.set(item._id, item); + searchIndexRef.current.add(item._id, item); + galleryItems.push(item); + } - if (next === undefined) { - break; // No more metadata. - } - else { - if (page.records.length === 0) { - // Assets are still loading, let them continue loading. - await sleep(500); - } - } - } + // Renders the assets that we know about already. + setAssets(applySort(galleryItems)); } // @@ -260,7 +250,7 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children throw new Error(`Cannot edit readonly gallery.`); } - if (!collectionId) { + if (!setId) { throw new Error(`Cannot add asset without a collection id.`); } @@ -276,22 +266,41 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // const ops: IDatabaseOp[] = [ { - databaseName: collectionId, collectionName: "metadata", + setId, recordId: galleryItem._id, op: { type: "set", - fields: galleryItemToAsset(galleryItem), + fields: { + _id: galleryItem._id, + width: galleryItem.width, + height: galleryItem.height, + origFileName: galleryItem.origFileName, + origPath: galleryItem.origPath, + contentType: galleryItem.contentType, + hash: galleryItem.hash, + location: galleryItem.location, + fileDate: galleryItem.fileDate, + photoDate: galleryItem.photoDate, + sortDate: galleryItem.sortDate, + uploadDate: dayjs().toISOString(), + properties: galleryItem.properties, + labels: galleryItem.labels, + description: galleryItem.description, + setId, + }, }, }, { - databaseName: collectionId, collectionName: "hashes", - recordId: galleryItem.hash, + setId, + recordId: uuid(), op: { - type: "push", - field: "assetIds", - value: galleryItem._id, + type: "set", + fields: { + assetId: galleryItem._id, + setId, + }, }, } ]; @@ -308,29 +317,6 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children }; } - // - // Converts a gallery item to an asset. - // - function galleryItemToAsset(galleryItem: IGalleryItem): IAsset { //todo: pull this up. No need for a sep function. - return { - _id: galleryItem._id, - width: galleryItem.width, - height: galleryItem.height, - origFileName: galleryItem.origFileName, - origPath: galleryItem.origPath, - contentType: galleryItem.contentType, - hash: galleryItem.hash, - location: galleryItem.location, - fileDate: galleryItem.fileDate, - photoDate: galleryItem.photoDate, - sortDate: galleryItem.sortDate, - uploadDate: dayjs().toISOString(), - properties: galleryItem.properties, - labels: galleryItem.labels, - description: galleryItem.description, - }; - } - // // Updates an asset in the gallery by index. // @@ -339,7 +325,7 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children throw new Error(`Cannot edit readonly gallery.`); } - if (!collectionId) { + if (!setId) { throw new Error(`Cannot edit asset without a collection id.`); } @@ -360,8 +346,8 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // Update the asset in the database. // const ops: IDatabaseOp[] = [{ - databaseName: collectionId, collectionName: "metadata", + setId, recordId: assetId, op: { type: "set", @@ -375,11 +361,11 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // Maps a hash to the assets already uploaded. // async function mapHashToAssets(hash: string): Promise { - if (!collectionId) { + if (!setId) { throw new Error(`Cannot check asset without a collection id.`); } - return await source.mapHashToAssets(collectionId, hash); + return await source.mapHashToAssets(setId, hash); } // @@ -390,11 +376,11 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children throw new Error(`Cannot upload to readonly gallery.`); } - if (!collectionId) { + if (!setId) { throw new Error(`Cannot upload asset without a collection id.`); } - await sink.storeAsset(collectionId, assetId, assetType, { + await sink.storeAsset(setId, assetId, assetType, { contentType, data }); @@ -404,18 +390,18 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // Loads data for an asset. // async function loadAsset(assetId: string, assetType: string): Promise { - if (!collectionId) { + if (!setId) { throw new Error(`Cannot load asset without a collection id.`); } - const key = `${collectionId}-${assetType}-${assetId}`; + const key = `${setId}-${assetType}-${assetId}`; const existingCacheEntry = assetCache.current.get(key); if (existingCacheEntry) { existingCacheEntry.numRefs += 1; return existingCacheEntry.objectUrl; } - const assetData = await source.loadAsset(collectionId, assetId, assetType); + const assetData = await source.loadAsset(setId, assetId, assetType); if (!assetData) { return undefined; } @@ -433,11 +419,11 @@ export function GalleryContextProvider({ source, sink, sortFn, groupFn, children // Unloads data for an asset. // function unloadAsset(assetId: string, assetType: string): void { - if (!collectionId) { + if (!setId) { throw new Error(`Cannot unload asset without a collection id.`); } - const key = `${collectionId}-${assetType}-${assetId}`; + const key = `${setId}-${assetType}-${assetId}`; const cacheEntry = assetCache.current.get(key); if (cacheEntry) { if (cacheEntry.numRefs === 1) { diff --git a/packages/user-interface/src/context/gallery-item-context.tsx b/packages/user-interface/src/context/gallery-item-context.tsx index 1a2dd2e..e4105ca 100644 --- a/packages/user-interface/src/context/gallery-item-context.tsx +++ b/packages/user-interface/src/context/gallery-item-context.tsx @@ -43,10 +43,6 @@ export interface IProps { export function GalleryItemContextProvider({ children, asset, assetIndex }: IProps) { - // - // todo: Register for update to the asset from the source to trigger a render. - // - const { updateAsset: updateGalleryAsset } = useGallery(); // diff --git a/packages/user-interface/src/context/indexeddb-context.tsx b/packages/user-interface/src/context/indexeddb-context.tsx index 0ca479f..d47e316 100644 --- a/packages/user-interface/src/context/indexeddb-context.tsx +++ b/packages/user-interface/src/context/indexeddb-context.tsx @@ -1,5 +1,5 @@ -import { IDatabaseConfigurations, IIndexeddbDatabases, IndexeddbDatabases } from "database"; import React, { ReactNode, createContext, useContext, useEffect, useRef } from "react"; +import { IIndexeddbDatabases, IDatabaseConfigurations, IndexeddbDatabases } from "../lib/indexeddb/indexeddb-databases"; export interface IIndexeddbContext { // @@ -32,19 +32,11 @@ const databaseConfigurations: IDatabaseConfigurations = { collectionNames: [ "outgoing-asset-upload", "outgoing-asset-update", - "last-update-id", + "last-update", "user", ], versionNumber: 1, }, - debug: { // For debugging. - collectionNames: [ - "updates-recieved", - "updates-sent", - "initial-sync-recieved", - ], - versionNumber: 1, - }, }; // diff --git a/packages/user-interface/src/context/source/cloud-gallery-sink.tsx b/packages/user-interface/src/context/source/cloud-gallery-sink.tsx deleted file mode 100644 index 1f3adfa..0000000 --- a/packages/user-interface/src/context/source/cloud-gallery-sink.tsx +++ /dev/null @@ -1,32 +0,0 @@ -//todo: prolly don't need this now! - -// -// Provides a sink for adding/updating assets in the cloud. -// - -import { IApi, IAssetData, IAssetSink, IDatabaseOp } from "database"; - -// -// Use the "Cloud sink" in a component. -// -export function useCloudGallerySink({ api }: { api: IApi }): IAssetSink { - - // - // Submits operations to change the database. - // - async function submitOperations(ops: IDatabaseOp[]): Promise { - await api.submitOperations(ops); - } - - // - // Stores an asset. - // - async function storeAsset(collectionId: string, assetId: string, assetType: string, assetData: IAssetData): Promise { - await api.uploadSingleAsset(collectionId, assetId, assetType, assetData); - } - - return { - submitOperations, - storeAsset, - }; -} diff --git a/packages/user-interface/src/context/source/cloud-gallery-source.tsx b/packages/user-interface/src/context/source/cloud-gallery-source.tsx deleted file mode 100644 index 0428068..0000000 --- a/packages/user-interface/src/context/source/cloud-gallery-source.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { IApi, IAsset, IAssetData, IAssetSource, IHashRecord, IPage } from "database"; - -// -// Provides a source of assets for the gallery from the cloud. -// -export function useCloudGallerySource({ api }: { api: IApi }): IAssetSource { - - // - // Loads metadata for all assets. - // - async function loadAssets(collectionId: string, max: number, next?: string): Promise> { - return await api.getAll(collectionId, "metadata", max, next); - } - - // - // Maps a hash to the assets already uploaded. - // - async function mapHashToAssets(collectionId: string, hash: string): Promise { - const hashRecord = await api.getOne(collectionId, "hashes", hash); - if (!hashRecord) { - return []; - } - - return hashRecord.assetIds; - } - - // - // Loads data for an asset. - // - async function loadAsset(collectionId: string, assetId: string, assetType: string): Promise { - const assetBlob = await api.getAsset(collectionId, assetId, assetType); - return { - contentType: assetBlob.type, - data: assetBlob, - }; - } - - return { - isInitialised: api.isInitialised, - loadAssets, - mapHashToAssets, - loadAsset, - }; -} diff --git a/packages/user-interface/src/context/source/indexeddb-gallery-sink.tsx b/packages/user-interface/src/context/source/indexeddb-gallery-sink.tsx deleted file mode 100644 index d6eb81b..0000000 --- a/packages/user-interface/src/context/source/indexeddb-gallery-sink.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// -// Provides a sink for adding/updating assets to indexeddb. -// - -import { IAssetRecord } from "../../def/asset-record"; -import { IAssetData, IAssetSink, IDatabaseOp, IIndexeddbDatabases, applyOperations } from "database"; - -// -// Use the "Indexeddb sink" in a component. -// -export function useIndexeddbGallerySink({ indexeddbDatabases }: { indexeddbDatabases: IIndexeddbDatabases }): IAssetSink { - - // - // Submits operations to change the database. - // - async function submitOperations(ops: IDatabaseOp[]): Promise { - await applyOperations(indexeddbDatabases, ops); - } - - // - // Stores an asset. - // - async function storeAsset(collectionId: string, assetId: string, assetType: string, assetData: IAssetData): Promise { - const assetCollection = indexeddbDatabases.database(collectionId); - await assetCollection.collection(assetType).setOne(assetId, { - _id: assetId, - storeDate: new Date(), - assetData, - }); - } - - return { - submitOperations, - storeAsset, - }; -} diff --git a/packages/user-interface/src/context/source/local-gallery-source.tsx b/packages/user-interface/src/context/source/local-gallery-source.tsx deleted file mode 100644 index ae851c0..0000000 --- a/packages/user-interface/src/context/source/local-gallery-source.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// -// Provides a source of assets for the gallery from indexeddb. -// - -import { IAsset, IAssetData, IAssetSink, IAssetSource, IPage } from "database"; -import { useOnline } from "../../lib/use-online"; - -// -// Use the "Local source" in a component. -// -export function useLocalGallerySource({ indexeddbSource, indexeddbSink, cloudSource }: { indexeddbSource: IAssetSource, indexeddbSink: IAssetSink, cloudSource: IAssetSource }): IAssetSource { - - const { isOnline } = useOnline(); - - // - // Loads metadata for all assets. - // - async function loadAssets(collectionId: string, max: number, next?: string): Promise> { - return await indexeddbSource.loadAssets(collectionId, max, next); - } - - // - // Maps a hash to the assets already uploaded. - // - async function mapHashToAssets(collectionId: string, hash: string): Promise { - return await indexeddbSource.mapHashToAssets(collectionId, hash); - } - - // - // Loads data for an asset. - // - async function loadAsset(collectionId: string, assetId: string, assetType: string): Promise { - const localAsset = await indexeddbSource.loadAsset(collectionId, assetId, assetType); - if (localAsset) { - return localAsset; - } - - if (!isOnline) { - return undefined; - } - - // Fallback to cloud. - const assetData = await cloudSource.loadAsset(collectionId, assetId, assetType); - if (assetData) { - // Cache the asset in indexeddb. - await indexeddbSink.storeAsset(collectionId, assetId, assetType, assetData); - } - return assetData; - } - - return { - isInitialised: indexeddbSource.isInitialised, - loadAssets, - mapHashToAssets, - loadAsset, - }; -} diff --git a/packages/user-interface/src/context/source/local-gallery-sink.tsx b/packages/user-interface/src/context/source/local-sink.tsx similarity index 52% rename from packages/user-interface/src/context/source/local-gallery-sink.tsx rename to packages/user-interface/src/context/source/local-sink.tsx index ba2eee2..ad6ed03 100644 --- a/packages/user-interface/src/context/source/local-gallery-sink.tsx +++ b/packages/user-interface/src/context/source/local-sink.tsx @@ -2,14 +2,17 @@ // Provides a sink for adding/updating assets to indexeddb. // -import { IAssetData, IAssetSink, IAssetUpdateRecord, IAssetUploadRecord, IDatabaseOp, IPersistentQueue } from "database"; +import { IDatabaseOp } from "defs"; +import { IAssetSink } from "../../lib/asset-sink"; +import { IPersistentQueue } from "../../lib/sync/persistent-queue"; +import { IAssetUploadRecord } from "../../lib/sync/asset-upload-record"; +import { IAssetUpdateRecord } from "../../lib/sync/asset-update-record"; +import { IAssetData } from "../../def/asset-data"; +import { IIndexeddbDatabases } from "../../lib/indexeddb/indexeddb-databases"; +import { applyOperations } from "../../lib/apply-operation"; +import { IAssetRecord } from "../../def/asset-record"; export interface IProps { - // - // Used to forward assets and updates to indexeddb. - // - indexeddbSink: IAssetSink; - // // Queues outgoing asset uploads. // @@ -19,12 +22,17 @@ export interface IProps { // Queues outgoing asset updates. // outgoingAssetUpdateQueue: IPersistentQueue; + + // + // Indexeddb databases. + // + indexeddbDatabases: IIndexeddbDatabases; }; // // Use the "Local sink" in a component. // -export function useLocalGallerySink({ indexeddbSink, outgoingAssetUploadQueue, outgoingAssetUpdateQueue }: IProps): IAssetSink { +export function useLocalGallerySink({ outgoingAssetUploadQueue, outgoingAssetUpdateQueue, indexeddbDatabases }: IProps): IAssetSink { // // Submits operations to change the database. @@ -33,7 +41,7 @@ export function useLocalGallerySink({ indexeddbSink, outgoingAssetUploadQueue, o // // Updates the local database. // - await indexeddbSink.submitOperations(ops); + await applyOperations(indexeddbDatabases, ops); // // Queue the updates for upload to the cloud. @@ -48,13 +56,18 @@ export function useLocalGallerySink({ indexeddbSink, outgoingAssetUploadQueue, o // // Store the asset locally. // - await indexeddbSink.storeAsset(collectionId, assetId, assetType, assetData); + const assetCollection = indexeddbDatabases.database(collectionId); + await assetCollection.collection(assetType).setOne(assetId, { + _id: assetId, + storeDate: new Date(), + assetData, + }); // // Queue the asset for upload to the cloud. // await outgoingAssetUploadQueue.add({ - collectionId, + setId: collectionId, assetId, assetType, assetData, diff --git a/packages/user-interface/src/context/source/indexeddb-gallery-source.tsx b/packages/user-interface/src/context/source/local-source.tsx similarity index 55% rename from packages/user-interface/src/context/source/indexeddb-gallery-source.tsx rename to packages/user-interface/src/context/source/local-source.tsx index c328f20..cdd17ee 100644 --- a/packages/user-interface/src/context/source/indexeddb-gallery-source.tsx +++ b/packages/user-interface/src/context/source/local-source.tsx @@ -2,20 +2,28 @@ // Provides a source of assets for the gallery from indexeddb. // +import { IAsset } from "defs"; +import { IAssetData } from "../../def/asset-data"; import { IAssetRecord } from "../../def/asset-record"; -import { IAsset, IAssetData, IAssetSource, IHashRecord, IIndexeddbDatabases, IPage } from "database"; +import { IHashRecord } from "../../def/hash-record"; +import { IApi } from "../../lib/api"; +import { IAssetSource } from "../../lib/asset-source"; +import { IIndexeddbDatabases } from "../../lib/indexeddb/indexeddb-databases"; +import { useOnline } from "../../lib/use-online"; // -// Use the "Indexeddb source" in a component. +// Use the "Local source" in a component. // -export function useIndexeddbGallerySource({ indexeddbDatabases }: { indexeddbDatabases: IIndexeddbDatabases }): IAssetSource { +export function useLocalGallerySource({ indexeddbDatabases, api }: { indexeddbDatabases: IIndexeddbDatabases, api: IApi }): IAssetSource { + + const { isOnline } = useOnline(); // // Loads metadata for all assets. // - async function loadAssets(collectionId: string, max: number, next?: string): Promise> { + async function loadAssets(collectionId: string): Promise { const assetDatabase = indexeddbDatabases.database(collectionId); - return await assetDatabase.collection("metadata").getAll(max, next); + return await assetDatabase.collection("metadata").getAll(); } // @@ -37,15 +45,23 @@ export function useIndexeddbGallerySource({ indexeddbDatabases }: { indexeddbDat async function loadAsset(collectionId: string, assetId: string, assetType: string): Promise { const assetCollection = indexeddbDatabases.database(collectionId); const assetRecord = await assetCollection.collection(assetType).getOne(assetId); - if (!assetRecord) { - return undefined; + if (assetRecord) { + return assetRecord.assetData; } - return assetRecord.assetData; + if (!isOnline) { + return undefined; + } + + // Fallback to cloud. + const assetBlob = await api.getAsset(collectionId, assetId, assetType); + return { + contentType: assetBlob.type, + data: assetBlob, + }; } return { - isInitialised: true, // Indexedb is always considered online. loadAssets, mapHashToAssets, loadAsset, diff --git a/packages/user-interface/src/context/upload-context.tsx b/packages/user-interface/src/context/upload-context.tsx index 64319d7..9a90300 100644 --- a/packages/user-interface/src/context/upload-context.tsx +++ b/packages/user-interface/src/context/upload-context.tsx @@ -13,7 +13,7 @@ import mimeTypes from "mime-types"; import { retry } from "../lib/retry"; import { base64StringToBlob } from "blob-util"; import { useGallery } from "./gallery-context"; -import { uuid } from "database"; +import { uuid } from "../lib/uuid"; // // Size of the thumbnail to generate and display during uploaded. diff --git a/packages/database/src/defs/asset-data.ts b/packages/user-interface/src/def/asset-data.ts similarity index 100% rename from packages/database/src/defs/asset-data.ts rename to packages/user-interface/src/def/asset-data.ts diff --git a/packages/user-interface/src/def/asset-record.ts b/packages/user-interface/src/def/asset-record.ts index d4b9b99..73be571 100644 --- a/packages/user-interface/src/def/asset-record.ts +++ b/packages/user-interface/src/def/asset-record.ts @@ -1,4 +1,4 @@ -import { IAssetData } from "database"; +import { IAssetData } from "./asset-data"; // // Specifies the local record for an asset. diff --git a/packages/database/src/defs/hash-record.ts b/packages/user-interface/src/def/hash-record.ts similarity index 72% rename from packages/database/src/defs/hash-record.ts rename to packages/user-interface/src/def/hash-record.ts index c08f6f2..6be6513 100644 --- a/packages/database/src/defs/hash-record.ts +++ b/packages/user-interface/src/def/hash-record.ts @@ -2,6 +2,11 @@ // Maps hashes to assets. // export interface IHashRecord { + // + // The hash. + // + _id: string; + // // Asset ids that map to this hash. // diff --git a/packages/database/src/defs/user.ts b/packages/user-interface/src/def/user.ts similarity index 75% rename from packages/database/src/defs/user.ts rename to packages/user-interface/src/def/user.ts index b1b1e27..c926308 100644 --- a/packages/database/src/defs/user.ts +++ b/packages/user-interface/src/def/user.ts @@ -1,7 +1,7 @@ // // Defines a user's collection. // -export interface ICollections { //todo: Share with the backend +export interface ISets { // // The default collection to upload to the user. // @@ -28,7 +28,7 @@ export interface IUser { _id: string; // - // Metadata for the user's collections. + // Metadata for the user's sets. // - collections: ICollections; + sets: ISets; } \ No newline at end of file diff --git a/packages/user-interface/src/index.tsx b/packages/user-interface/src/index.tsx index f21ae4a..cf188e9 100644 --- a/packages/user-interface/src/index.tsx +++ b/packages/user-interface/src/index.tsx @@ -1,12 +1,8 @@ export { Main } from './main'; export { AuthContextProvider, isProduction } from './context/auth-context'; export { ApiContextProvider, useApi } from './context/api-context'; -export { useCloudGallerySource } from './context/source/cloud-gallery-source'; -export { useCloudGallerySink } from './context/source/cloud-gallery-sink'; -export { useLocalGallerySource } from './context/source/local-gallery-source'; -export { useLocalGallerySink } from './context/source/local-gallery-sink'; -export { useIndexeddbGallerySource } from './context/source/indexeddb-gallery-source'; -export { useIndexeddbGallerySink } from './context/source/indexeddb-gallery-sink'; +export { useLocalGallerySource } from './context/source/local-source'; +export { useLocalGallerySink } from './context/source/local-sink'; export { useDatabaseSync } from './context/database-sync'; export { GalleryContextProvider } from './context/gallery-context'; export { GalleryItemContextProvider } from './context/gallery-item-context'; @@ -21,3 +17,13 @@ export * from "./lib/image"; export * from "./lib/sleep"; export * from "./lib/reverse-geocode"; export * from "./lib/retry"; +export * as indexeddb from "./lib/indexeddb/indexeddb"; +export * from "./lib/indexeddb/indexeddb-databases"; +export * from "./lib/indexeddb/indexeddb-database"; +export * from "./lib/indexeddb/indexeddb-database-collection"; +export * from "./lib/sync/persistent-queue"; +export * from "./lib/sync/asset-update-record"; +export * from "./lib/sync/asset-upload-record"; +export * from "./lib/asset-source"; +export * from "./def/asset-data"; +export * from "./lib/uuid"; diff --git a/packages/user-interface/src/lib/api.ts b/packages/user-interface/src/lib/api.ts new file mode 100644 index 0000000..c5c8604 --- /dev/null +++ b/packages/user-interface/src/lib/api.ts @@ -0,0 +1,70 @@ +import { IDatabaseOp } from "defs"; +import { IRecord } from "./database-collection"; +import { IUser } from "../def/user"; +import { IAssetData } from "../def/asset-data"; + +// +// The result of get the database journal. +// +export interface IJournalResult { + // + // Operations recorded against the collection. + // + journalRecords: IDatabaseOp[]; + + // + // The id of the latest update that has been retreived. + // + latestTime: string; +} + +// +// Client-side interface to the Photosphere API. +// +export interface IApi { + + // + // Set to true once the api is ready to use. + // + isInitialised: boolean; + + // + // Loads the user's details. + // + getUser(): Promise; + + // + // Retreives the latest time for the server. + // + getLatestTime(): Promise; + + // + // Retreives the data for an asset from the backend. + // + getAsset(setId: string, assetId: string, assetType: string): Promise; + + // + // Uploads an asset to the backend. + // + uploadSingleAsset(setId: string, assetId: string, assetType: string, assetData: IAssetData): Promise; + + // + // Submits database operations to the cloud. + // + submitOperations(ops: IDatabaseOp[]): Promise; + + // + // Gets the journal of operations that have been applied to the database. + // + getJournal(lastUpdateTime?: string): Promise; + + // + // Gets one record by id. + // + getOne(collectionName: string, id: string): Promise; + + // + // Gets a page of records from the database. + // + getAll(setId: string, collectionName: string, skip: number, limit: number): Promise; +} \ No newline at end of file diff --git a/packages/database/src/lib/apply-operation.ts b/packages/user-interface/src/lib/apply-operation.ts similarity index 51% rename from packages/database/src/lib/apply-operation.ts rename to packages/user-interface/src/lib/apply-operation.ts index 25d2fa3..26c0c1b 100644 --- a/packages/database/src/lib/apply-operation.ts +++ b/packages/user-interface/src/lib/apply-operation.ts @@ -1,9 +1,8 @@ -import dayjs from "dayjs"; -import { IDatabaseOp, IDatabaseOpRecord, IOpSelection } from "../defs/ops"; import { createReverseChronoTimestamp } from "./timestamp"; import { IDatabase } from "./database"; -import { IDatabaseCollection } from "./database-collection"; import { IDatabases } from "./databases"; +import { uuid } from "./uuid"; +import { IDatabaseOp, IDatabaseOpRecord, IOpSelection } from "defs"; // // Applies a single database operation to the field set for a database record. @@ -46,62 +45,24 @@ export function applyOperation(op: IOpSelection, fields: any): void { } } -// -// Applies an operation to a database collection. -// -export async function applyOperationToCollection(collection: IDatabaseCollection, databaseOp: IDatabaseOp): Promise { - const record = await collection.getOne(databaseOp.recordId); - - let updatedAsset = record as any || {}; - - if (!record) { - // Set the asset id when upserting. - updatedAsset._id = databaseOp.recordId; - } - - applyOperation(databaseOp.op, updatedAsset); - - await collection.setOne(databaseOp.recordId, updatedAsset); -} - -// -// Applies an operation to the database. -// -export async function applyOperationToDb(database: IDatabase, databaseOp: IDatabaseOp, clientId: string): Promise { - const databaseOpRecord: IDatabaseOpRecord = { - serverTime: dayjs().toISOString(), - clientId, - collectionName: databaseOp.collectionName, - recordId: databaseOp.recordId, - op: databaseOp.op, - }; - - const journalRecordId = createReverseChronoTimestamp(new Date()); - const journalCollection = database.collection("journal"); - await journalCollection.setOne(journalRecordId, databaseOpRecord); - - const recordCollection = database.collection(databaseOp.collectionName); - await applyOperationToCollection(recordCollection, databaseOp); -} - // // Submits operations to change various databases. // export async function applyOperations(databases: IDatabases, databaseOps: IDatabaseOp[]): Promise { for (const databaseOp of databaseOps) { - const recordId = databaseOp.recordId; - const database = databases.database(databaseOp.databaseName); + const database = databases.database(databaseOp.setId); const collection = database.collection(databaseOp.collectionName) - const record = await collection.getOne(recordId); + const record = await collection.getOne(databaseOp.recordId); let fields = record as any || {}; if (!record) { // Set the record id when upserting. - fields._id = recordId; + fields._id = databaseOp.recordId; + fields.setId = databaseOp.setId; } applyOperation(databaseOp.op, fields); - await collection.setOne(recordId, fields); + await collection.setOne(databaseOp.recordId, fields); } } diff --git a/packages/database/src/lib/asset-sink.ts b/packages/user-interface/src/lib/asset-sink.ts similarity index 79% rename from packages/database/src/lib/asset-sink.ts rename to packages/user-interface/src/lib/asset-sink.ts index 14193fc..20581dd 100644 --- a/packages/database/src/lib/asset-sink.ts +++ b/packages/user-interface/src/lib/asset-sink.ts @@ -1,5 +1,5 @@ -import { IAssetData } from "../defs/asset-data"; -import { IDatabaseOp } from "../defs/ops"; +import { IDatabaseOp } from "defs"; +import { IAssetData } from "../def/asset-data"; // // Stores assets to a particular location. diff --git a/packages/database/src/lib/asset-source.ts b/packages/user-interface/src/lib/asset-source.ts similarity index 58% rename from packages/database/src/lib/asset-source.ts rename to packages/user-interface/src/lib/asset-source.ts index df17855..0edb24b 100644 --- a/packages/database/src/lib/asset-source.ts +++ b/packages/user-interface/src/lib/asset-source.ts @@ -1,22 +1,16 @@ -import { IAsset } from "../defs/asset"; -import { IAssetData } from "../defs/asset-data"; -import { IPage } from "../defs/page"; +import { IAsset } from "defs"; +import { IAssetData } from "../def/asset-data"; // // Loads assets from a particular location. // export interface IAssetSource { - // - // Set to true when the source is initialised. - // - isInitialised: boolean; - // // Loads metadata for all assets. // - loadAssets(collectionId: string, max: number, next?: string): Promise>; + loadAssets(collectionId: string): Promise; // // Maps a hash to the assets already uploaded. diff --git a/packages/database/src/lib/binary-search.ts b/packages/user-interface/src/lib/binary-search.ts similarity index 100% rename from packages/database/src/lib/binary-search.ts rename to packages/user-interface/src/lib/binary-search.ts diff --git a/packages/user-interface/src/lib/database-collection.ts b/packages/user-interface/src/lib/database-collection.ts new file mode 100644 index 0000000..c1805da --- /dev/null +++ b/packages/user-interface/src/lib/database-collection.ts @@ -0,0 +1,32 @@ +// +// A page of records from the database. +// +export interface IRecord { + _id: string; +} + +// +// Implements a collection of records in the database. +// +export interface IDatabaseCollection { + + // + // Sets a new record to the database. + // + setOne(id: string, record: RecordT): Promise; + + // + // Gets one record by id. + // + getOne(id: string): Promise; + + // + // Gets all records from the database. + // + getAll(): Promise; + + // + // Deletes a record from the database. + // + deleteOne(id: string): Promise; +} diff --git a/packages/user-interface/src/lib/database.ts b/packages/user-interface/src/lib/database.ts new file mode 100644 index 0000000..3a92cbf --- /dev/null +++ b/packages/user-interface/src/lib/database.ts @@ -0,0 +1,11 @@ +import { IDatabaseCollection, IRecord } from "./database-collection"; + +// +// Implements a database. +// +export interface IDatabase { + // + // Gets a database collection by name. + // + collection(name: string): IDatabaseCollection; +} diff --git a/packages/database/src/lib/databases.ts b/packages/user-interface/src/lib/databases.ts similarity index 79% rename from packages/database/src/lib/databases.ts rename to packages/user-interface/src/lib/databases.ts index cc60e8a..86b7074 100644 --- a/packages/database/src/lib/databases.ts +++ b/packages/user-interface/src/lib/databases.ts @@ -1,4 +1,3 @@ -import { IDatabaseOp } from "../defs/ops"; import { IDatabase } from "./database"; export interface IDatabases { diff --git a/packages/user-interface/src/lib/gallery-item.ts b/packages/user-interface/src/lib/gallery-item.ts index 556c5f6..528f5b2 100644 --- a/packages/user-interface/src/lib/gallery-item.ts +++ b/packages/user-interface/src/lib/gallery-item.ts @@ -1,10 +1,10 @@ // // Represents an asset that can be displayed in the gallery. +// -import { IAsset } from "database"; +import { IAsset } from "defs"; -// -export interface IGalleryItem extends IAsset { +export interface IGalleryItem extends Omit { // // The computed width of the thumbnail. @@ -51,7 +51,6 @@ export interface IGalleryRow { // The group displayed in this row of items, if any. // group?: string; - } // diff --git a/packages/user-interface/src/lib/indexeddb/indexeddb-database-collection.ts b/packages/user-interface/src/lib/indexeddb/indexeddb-database-collection.ts new file mode 100644 index 0000000..dcbcf59 --- /dev/null +++ b/packages/user-interface/src/lib/indexeddb/indexeddb-database-collection.ts @@ -0,0 +1,43 @@ +import { IDatabaseCollection, IRecord } from "../database-collection"; +import { deleteRecord, getAllRecords, getRecord, storeRecord } from "./indexeddb"; + +export interface IIndexeddbDatabaseCollection extends IDatabaseCollection { +} + +export class IndexeddbDatabaseCollection implements IIndexeddbDatabaseCollection { + + constructor(private collectionName: string, private openDb: () => Promise) { + } + + // + // Sets a new record to the database. + // + async setOne(id: string, record: RecordT): Promise { + const db = await this.openDb(); + await storeRecord(db, this.collectionName, id, record); + } + + // + // Gets one record by id. + // + async getOne(id: string): Promise { + const db = await this.openDb(); + return await getRecord(db, this.collectionName, id); + } + + // + // Gets a page of records from the database. + // + async getAll(): Promise { + const db = await this.openDb(); + return await getAllRecords(db, this.collectionName); + } + + // + // Deletes a database record. + // + async deleteOne(recordId: string): Promise { + const db = await this.openDb(); + await deleteRecord(db, this.collectionName, recordId); + } +} \ No newline at end of file diff --git a/packages/database/src/lib/indexeddb/indexeddb-database.ts b/packages/user-interface/src/lib/indexeddb/indexeddb-database.ts similarity index 83% rename from packages/database/src/lib/indexeddb/indexeddb-database.ts rename to packages/user-interface/src/lib/indexeddb/indexeddb-database.ts index 93d0117..8492cc2 100644 --- a/packages/database/src/lib/indexeddb/indexeddb-database.ts +++ b/packages/user-interface/src/lib/indexeddb/indexeddb-database.ts @@ -1,4 +1,5 @@ import { IDatabase } from "../database"; +import { IRecord } from "../database-collection"; import { IIndexeddbDatabaseCollection, IndexeddbDatabaseCollection } from "./indexeddb-database-collection"; export interface IIndexeddbDatabase extends IDatabase { @@ -18,7 +19,7 @@ export class IndexeddbDatabase implements IIndexeddbDatabase { // // Gets a database collection by name. // - collection(collectionName: string): IIndexeddbDatabaseCollection { + collection(collectionName: string): IIndexeddbDatabaseCollection { return new IndexeddbDatabaseCollection(collectionName, this.openDb); } diff --git a/packages/database/src/lib/indexeddb/indexeddb-databases.ts b/packages/user-interface/src/lib/indexeddb/indexeddb-databases.ts similarity index 100% rename from packages/database/src/lib/indexeddb/indexeddb-databases.ts rename to packages/user-interface/src/lib/indexeddb/indexeddb-databases.ts diff --git a/packages/database/src/lib/indexeddb/indexeddb.ts b/packages/user-interface/src/lib/indexeddb/indexeddb.ts similarity index 100% rename from packages/database/src/lib/indexeddb/indexeddb.ts rename to packages/user-interface/src/lib/indexeddb/indexeddb.ts diff --git a/packages/database/src/lib/sync/asset-update-record.ts b/packages/user-interface/src/lib/sync/asset-update-record.ts similarity index 79% rename from packages/database/src/lib/sync/asset-update-record.ts rename to packages/user-interface/src/lib/sync/asset-update-record.ts index 69f02c1..a4c4aeb 100644 --- a/packages/database/src/lib/sync/asset-update-record.ts +++ b/packages/user-interface/src/lib/sync/asset-update-record.ts @@ -1,4 +1,4 @@ -import { IDatabaseOp } from "../../defs/ops"; +import { IDatabaseOp } from "defs"; // // Records an asset update in the outgoing queue. diff --git a/packages/database/src/lib/sync/asset-upload-record.ts b/packages/user-interface/src/lib/sync/asset-upload-record.ts similarity index 81% rename from packages/database/src/lib/sync/asset-upload-record.ts rename to packages/user-interface/src/lib/sync/asset-upload-record.ts index e6473e4..8ac9745 100644 --- a/packages/database/src/lib/sync/asset-upload-record.ts +++ b/packages/user-interface/src/lib/sync/asset-upload-record.ts @@ -1,4 +1,4 @@ -import { IAssetData } from "../../defs/asset-data"; +import { IAssetData } from "../../def/asset-data"; // // Records an asset upload in the outgoing queue. @@ -7,7 +7,7 @@ export interface IAssetUploadRecord { // // ID of the collection to upload to. // - collectionId: string; + setId: string; // // ID of the asset. diff --git a/packages/user-interface/src/lib/sync/last-update-record.ts b/packages/user-interface/src/lib/sync/last-update-record.ts new file mode 100644 index 0000000..ed32713 --- /dev/null +++ b/packages/user-interface/src/lib/sync/last-update-record.ts @@ -0,0 +1,14 @@ +// +// Records the time the last update was read from the server. +// +export interface ILastUpdateRecord { + // + // The ID of the record. + // + _id: string; + + // + // The time of the last update. + // + lastUpdateTime: string; +} \ No newline at end of file diff --git a/packages/database/src/lib/sync/persistent-queue.ts b/packages/user-interface/src/lib/sync/persistent-queue.ts similarity index 100% rename from packages/database/src/lib/sync/persistent-queue.ts rename to packages/user-interface/src/lib/sync/persistent-queue.ts diff --git a/packages/user-interface/src/lib/sync/sync-incoming.ts b/packages/user-interface/src/lib/sync/sync-incoming.ts new file mode 100644 index 0000000..86de8d1 --- /dev/null +++ b/packages/user-interface/src/lib/sync/sync-incoming.ts @@ -0,0 +1,53 @@ +import { IApi } from "../api"; +import { applyOperations } from "../apply-operation"; +import { IIndexeddbDatabases } from "../indexeddb/indexeddb-databases"; +import { ILastUpdateRecord } from "./last-update-record"; + +interface IProps { + // + // Collections to synchronize. + // + setIds: string[]; + + // + // The interface to the backend. + // + api: IApi; + + // + // Indexeddb databases. + // + indexeddbDatabases: IIndexeddbDatabases; +} + +// +// Receive incoming asset updates from the cloud. +// +export async function syncIncoming({ setIds, api, indexeddbDatabases }: IProps): Promise { + + const userDatabase = indexeddbDatabases.database("user"); + const lastUpdateCollection = userDatabase.collection("last-update"); + + for (const setId of setIds) { + const lastUpdateRecord = await lastUpdateCollection.getOne(setId); + const journalResult = await api.getJournal(lastUpdateRecord?.lastUpdateTime); + if (journalResult.journalRecords.length > 0) { + // + // Apply incoming changes to the local database. + // + await applyOperations(indexeddbDatabases, journalResult.journalRecords); + } + + if (journalResult.latestTime !== undefined) { + // + // Record the latest update that was received. + // + await lastUpdateCollection.setOne(setId, { + _id: setId, + lastUpdateTime: journalResult.latestTime + }); + } + + console.log(`Processed incoming updates for ${setId}, ${journalResult.journalRecords.length} ops`); + } +} diff --git a/packages/user-interface/src/lib/sync/sync-initial.ts b/packages/user-interface/src/lib/sync/sync-initial.ts new file mode 100644 index 0000000..ac8dcb9 --- /dev/null +++ b/packages/user-interface/src/lib/sync/sync-initial.ts @@ -0,0 +1,74 @@ +import { IApi } from "../api"; +import { IIndexeddbDatabases } from "../indexeddb/indexeddb-databases"; +import { uuid } from "../uuid"; +import { ILastUpdateRecord } from "./last-update-record"; + +interface IProps { + // + // Collections to synchronize. + // + setIds: string[]; + + // + // The interface to the backend. + // + api: IApi; + + // + // Local databases to poplulate. + // + indexeddbDatabases: IIndexeddbDatabases; +} + +// +// Perform the initial database synchronization. +// +export async function initialSync({ setIds, api, indexeddbDatabases }: IProps): Promise { + + const userDatabase = indexeddbDatabases.database("user"); + + // + // Records the time of the latest update for the set. + // This should be done before the initial sync to avoid missing updates. + // + const latestTime = await api.getLatestTime(); + + for (const setId of setIds) { + const localDatabase = indexeddbDatabases.database(setId); + const assets = await localDatabase.collection("metadata").getAll(); //TODO: There is probably a more efficient way to probe the db. + if (assets.length > 0) { + // If we have assets in this collection we have already sync'd it. + continue; + } + + if (latestTime !== undefined) { + // + // Record the latest time where updates were received. + // + userDatabase.collection("last-update").setOne(setId, { + _id: uuid(), + lastUpdateTime: latestTime, + }); + } + + for (const collectionName of ["metadata", "hashes"]) { + let skip = 0; + const pageSize = 1000; + while (true) { + const records = await api.getAll(setId, collectionName, skip, pageSize); + if (records.length === 0) { + // No more records. + break; + } + + skip += pageSize; + + const localCollection = localDatabase.collection(collectionName); + for (const record of records) { + await localCollection.setOne(record._id, record); // Store it locally. + } + } + } + } +} + diff --git a/packages/database/src/lib/sync/sync-outgoing.ts b/packages/user-interface/src/lib/sync/sync-outgoing.ts similarity index 67% rename from packages/database/src/lib/sync/sync-outgoing.ts rename to packages/user-interface/src/lib/sync/sync-outgoing.ts index 20c83cd..3e211ba 100644 --- a/packages/database/src/lib/sync/sync-outgoing.ts +++ b/packages/user-interface/src/lib/sync/sync-outgoing.ts @@ -1,13 +1,9 @@ -import { IAssetSink } from "../asset-sink"; +import { IApi } from "../api"; import { IAssetUpdateRecord } from "./asset-update-record"; import { IAssetUploadRecord } from "./asset-upload-record"; import { IPersistentQueue } from "./persistent-queue"; interface IProps { - // - // Sink for sending assets and updates to the cloud. - // - cloudSink: IAssetSink; // // Queue of outgoing asset uploads. @@ -18,12 +14,17 @@ interface IProps { // Queue of outgoing asset updates. // outgoingAssetUpdateQueue: IPersistentQueue; + + // + // The interface to the backend. + // + api: IApi; } // // Send outgoing asset uploads and updates to the cloud. // -export async function syncOutgoing({ cloudSink, outgoingAssetUploadQueue, outgoingAssetUpdateQueue }: IProps): Promise { +export async function syncOutgoing({ outgoingAssetUploadQueue, outgoingAssetUpdateQueue, api }: IProps): Promise { // // Flush the queue of outgoing asset uploads. // @@ -33,10 +34,10 @@ export async function syncOutgoing({ cloudSink, outgoingAssetUploadQueue, outgoi break; } - await cloudSink.storeAsset(outgoingUpload.collectionId, outgoingUpload.assetId, outgoingUpload.assetType, outgoingUpload.assetData); + await api.uploadSingleAsset(outgoingUpload.setId, outgoingUpload.assetId, outgoingUpload.assetType, outgoingUpload.assetData); await outgoingAssetUploadQueue.removeNext(); - console.log(`Processed outgoing upload: ${outgoingUpload.collectionId}/${outgoingUpload.assetType}/${outgoingUpload.assetId}`); + console.log(`Processed outgoing upload: ${outgoingUpload.setId}/${outgoingUpload.assetType}/${outgoingUpload.assetId}`); } // @@ -48,12 +49,12 @@ export async function syncOutgoing({ cloudSink, outgoingAssetUploadQueue, outgoi break; } - await cloudSink.submitOperations(outgoingUpdate.ops); + await api.submitOperations(outgoingUpdate.ops); await outgoingAssetUpdateQueue.removeNext(); console.log(`Processed outgoing updates:`); for (const op of outgoingUpdate.ops) { - console.log(` ${op.databaseName}/${op.collectionName}/${op.recordId}`); + console.log(` ${op.collectionName}/${op.recordId}`); } } } \ No newline at end of file diff --git a/packages/database/src/lib/timestamp.ts b/packages/user-interface/src/lib/timestamp.ts similarity index 100% rename from packages/database/src/lib/timestamp.ts rename to packages/user-interface/src/lib/timestamp.ts diff --git a/packages/user-interface/src/lib/use-client-id.ts b/packages/user-interface/src/lib/use-client-id.ts index 354559e..6bd5be3 100644 --- a/packages/user-interface/src/lib/use-client-id.ts +++ b/packages/user-interface/src/lib/use-client-id.ts @@ -1,4 +1,4 @@ -import { uuid } from "database"; +import { uuid } from "./uuid"; function getClientId() { const existingClientId = localStorage.getItem("clientId"); diff --git a/packages/user-interface/src/lib/uuid.ts b/packages/user-interface/src/lib/uuid.ts new file mode 100644 index 0000000..d35a244 --- /dev/null +++ b/packages/user-interface/src/lib/uuid.ts @@ -0,0 +1,7 @@ + +// +// Generates a unique id. +// +export function uuid(): string { + return crypto.randomUUID(); +} \ No newline at end of file diff --git a/packages/database/src/tests/lib/apply-operation.test.ts b/packages/user-interface/src/test/lib/apply-operation.test.ts similarity index 52% rename from packages/database/src/tests/lib/apply-operation.test.ts rename to packages/user-interface/src/test/lib/apply-operation.test.ts index 9200c5a..abb64f6 100644 --- a/packages/database/src/tests/lib/apply-operation.test.ts +++ b/packages/user-interface/src/test/lib/apply-operation.test.ts @@ -1,5 +1,4 @@ -import exp from "constants"; -import { applyOperation, applyOperationToCollection, applyOperationToDb } from "../../lib/apply-operation"; +import { applyOperation } from "../../lib/apply-operation"; describe("apply operation", () => { test("can set field", () => { @@ -72,74 +71,3 @@ describe("apply operation", () => { }); }); -describe("apply operation to collection", () => { - test("can apply operation", async () => { - const mockCollection: any = { - getOne: async () => ({}), - setOne: jest.fn(), - }; - await applyOperationToCollection(mockCollection, { - databaseName: "XYZ", - collectionName: "ABC", - recordId: "123", - op: { - type: "set", - fields: { - name: "Alice" - }, - } - }); - expect(mockCollection.setOne).toHaveBeenCalledWith("123", { name: "Alice" }); - }); - -}); - -describe("apply operation to database", () => { - test("can apply operation", async () => { - const mockJournal: any = { - setOne: jest.fn(), - }; - const mockCollection: any = { - getOne: async () => ({}), - setOne: jest.fn(), - }; - const mockDatabase: any = { - collection(collectionName: string) { - if (collectionName === "journal") { - return mockJournal; - } - - if (collectionName === "ABC") { - return mockCollection; - } - - throw new Error(`Unknown collection: ${collectionName}`); - }, - }; - await applyOperationToDb(mockDatabase, { - databaseName: "XYZ", - collectionName: "ABC", - recordId: "123", - op: { - type: "set", - fields: { - name: "Alice" - }, - } - }, "some-client"); - - expect(mockJournal.setOne).toHaveBeenCalledWith(expect.any(String), { - clientId: "some-client", - collectionName: "ABC", - recordId: "123", - op: { - type: "set", - fields: { - name: "Alice", - }, - }, - serverTime: expect.any(String), - }); - expect(mockCollection.setOne).toHaveBeenCalledWith("123", { name: "Alice" }); - }); -}); \ No newline at end of file diff --git a/packages/database/src/tests/lib/binary-search.test.ts b/packages/user-interface/src/test/lib/binary-search.test.ts similarity index 100% rename from packages/database/src/tests/lib/binary-search.test.ts rename to packages/user-interface/src/test/lib/binary-search.test.ts diff --git a/packages/database/src/tests/lib/timestamp.test.ts b/packages/user-interface/src/test/lib/timestamp.test.ts similarity index 100% rename from packages/database/src/tests/lib/timestamp.test.ts rename to packages/user-interface/src/test/lib/timestamp.test.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35eda0b..b9066d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,12 +16,12 @@ importers: cors: specifier: ^2.8.5 version: 2.8.5 - database: - specifier: workspace:* - version: link:../packages/database dayjs: specifier: ^1.11.7 version: 1.11.10 + defs: + specifier: workspace:* + version: link:../packages/defs express: specifier: ^5.0.0-beta.1 version: 5.0.0-beta.1 @@ -31,6 +31,9 @@ importers: fs-extra: specifier: ^11.1.0 version: 11.2.0 + mongodb: + specifier: ^4.7.0 + version: 4.17.2(@aws-sdk/client-sso-oidc@3.592.0) devDependencies: '@types/cors': specifier: ^2.8.13 @@ -56,6 +59,9 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 + insta-mongo: + specifier: ^0.0.6 + version: 0.0.6(@aws-sdk/client-sso-oidc@3.592.0) jest: specifier: ^29.4.1 version: 29.7.0(@types/node@18.19.21) @@ -113,12 +119,12 @@ importers: blob-util: specifier: ^2.0.2 version: 2.0.2 - database: - specifier: workspace:* - version: link:../../packages/database dayjs: specifier: ^1.11.7 version: 1.11.10 + defs: + specifier: workspace:* + version: link:../../packages/defs jszip: specifier: ^3.10.1 version: 3.10.1 @@ -283,9 +289,6 @@ importers: css-loader: specifier: ^6.10.0 version: 6.10.0(webpack@5.90.3) - database: - specifier: workspace:* - version: link:../packages/database fs-extra: specifier: ^11.1.0 version: 11.2.0 @@ -374,12 +377,12 @@ importers: blob-util: specifier: ^2.0.2 version: 2.0.2 - database: - specifier: workspace:* - version: link:../../packages/database dayjs: specifier: ^1.11.7 version: 1.11.10 + defs: + specifier: workspace:* + version: link:../../packages/defs jszip: specifier: ^3.10.1 version: 3.10.1 @@ -472,24 +475,11 @@ importers: specifier: ^5.0.2 version: 5.0.2(webpack-cli@5.1.4)(webpack@5.90.3) - packages/database: - dependencies: - dayjs: - specifier: ^1.11.7 - version: 1.11.10 + packages/defs: devDependencies: - '@types/jest': - specifier: ^29.4.0 - version: 29.5.12 '@types/node': specifier: ^20.3.1 version: 20.11.24 - jest: - specifier: ^29.0.1 - version: 29.7.0(@types/node@20.11.24) - ts-jest: - specifier: ^29.0.5 - version: 29.1.2(@babel/core@7.24.0)(jest@29.7.0)(typescript@4.9.5) typescript: specifier: ^4.9.4 version: 4.9.5 @@ -508,12 +498,12 @@ importers: blob-util: specifier: ^2.0.2 version: 2.0.2 - database: - specifier: workspace:* - version: link:../database dayjs: specifier: ^1.11.7 version: 1.11.10 + defs: + specifier: workspace:* + version: link:../defs flexsearch: specifier: ^0.7.43 version: 0.7.43 @@ -639,9 +629,6 @@ importers: css-loader: specifier: ^6.10.0 version: 6.10.0(webpack@5.90.3) - database: - specifier: workspace:* - version: link:../packages/database fs-extra: specifier: ^11.1.0 version: 11.2.0 @@ -684,12 +671,12 @@ importers: axios: specifier: ^0.27.2 version: 0.27.2 - database: - specifier: workspace:* - version: link:../../packages/database dayjs: specifier: ^1.11.7 version: 1.11.10 + defs: + specifier: workspace:* + version: link:../../packages/defs exif-parser: specifier: ^0.1.12 version: 0.1.12 @@ -753,6 +740,551 @@ packages: resolution: {integrity: sha512-NMTBNuuG4g3rame1aCnNS5qFYIzsTUV5qTFPRfTyYFS1feS6jsCBR+eTq9YkxCp1yuoM2UIcjunPaoPl77U9xQ==} dev: false + /@aws-crypto/ie11-detection@3.0.0: + resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + requiresBuild: true + dependencies: + tslib: 1.14.1 + optional: true + + /@aws-crypto/sha256-browser@3.0.0: + resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + requiresBuild: true + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-locate-window': 3.568.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + optional: true + + /@aws-crypto/sha256-js@3.0.0: + resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + requiresBuild: true + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.577.0 + tslib: 1.14.1 + optional: true + + /@aws-crypto/supports-web-crypto@3.0.0: + resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + requiresBuild: true + dependencies: + tslib: 1.14.1 + optional: true + + /@aws-crypto/util@3.0.0: + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + optional: true + + /@aws-sdk/client-cognito-identity@3.592.0: + resolution: {integrity: sha512-mk3JOBsk5hlrLTZFuoGIhFKFflOdxqMKmOgyUFs5+gBLuH0/lN3wNWJxk+BiY1nHzkxhBND1hDHc5dvZRugBJA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/client-sts': 3.592.0 + '@aws-sdk/core': 3.592.0 + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/middleware-host-header': 3.577.0 + '@aws-sdk/middleware-logger': 3.577.0 + '@aws-sdk/middleware-recursion-detection': 3.577.0 + '@aws-sdk/middleware-user-agent': 3.587.0 + '@aws-sdk/region-config-resolver': 3.587.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-endpoints': 3.587.0 + '@aws-sdk/util-user-agent-browser': 3.577.0 + '@aws-sdk/util-user-agent-node': 3.587.0 + '@smithy/config-resolver': 3.0.1 + '@smithy/core': 2.2.0 + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-retry': 3.0.3 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.3 + '@smithy/util-defaults-mode-node': 3.0.3 + '@smithy/util-endpoints': 2.0.1 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + optional: true + + /@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0): + resolution: {integrity: sha512-11Zvm8nm0s/UF3XCjzFRpQU+8FFVW5rcr3BHfnH6xAe5JEoN6bJN/n+wOfnElnjek+90hh+Qc7s141AMrCjiiw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.592.0 + '@aws-sdk/core': 3.592.0 + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/middleware-host-header': 3.577.0 + '@aws-sdk/middleware-logger': 3.577.0 + '@aws-sdk/middleware-recursion-detection': 3.577.0 + '@aws-sdk/middleware-user-agent': 3.587.0 + '@aws-sdk/region-config-resolver': 3.587.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-endpoints': 3.587.0 + '@aws-sdk/util-user-agent-browser': 3.577.0 + '@aws-sdk/util-user-agent-node': 3.587.0 + '@smithy/config-resolver': 3.0.1 + '@smithy/core': 2.2.0 + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-retry': 3.0.3 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.3 + '@smithy/util-defaults-mode-node': 3.0.3 + '@smithy/util-endpoints': 2.0.1 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sts' + - aws-crt + optional: true + + /@aws-sdk/client-sso@3.592.0: + resolution: {integrity: sha512-w+SuW47jQqvOC7fonyjFjsOh3yjqJ+VpWdVrmrl0E/KryBE7ho/Wn991Buf/EiHHeJikoWgHsAIPkBH29+ntdA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.592.0 + '@aws-sdk/middleware-host-header': 3.577.0 + '@aws-sdk/middleware-logger': 3.577.0 + '@aws-sdk/middleware-recursion-detection': 3.577.0 + '@aws-sdk/middleware-user-agent': 3.587.0 + '@aws-sdk/region-config-resolver': 3.587.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-endpoints': 3.587.0 + '@aws-sdk/util-user-agent-browser': 3.577.0 + '@aws-sdk/util-user-agent-node': 3.587.0 + '@smithy/config-resolver': 3.0.1 + '@smithy/core': 2.2.0 + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-retry': 3.0.3 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.3 + '@smithy/util-defaults-mode-node': 3.0.3 + '@smithy/util-endpoints': 2.0.1 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + optional: true + + /@aws-sdk/client-sts@3.592.0: + resolution: {integrity: sha512-KUrOdszZfcrlpKr4dpdkGibZ/qq3Lnfu1rjv1U+V1QJQ9OuMo9J3sDWpWV9tigNqY0aGllarWH5cJbz9868W/w==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/core': 3.592.0 + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/middleware-host-header': 3.577.0 + '@aws-sdk/middleware-logger': 3.577.0 + '@aws-sdk/middleware-recursion-detection': 3.577.0 + '@aws-sdk/middleware-user-agent': 3.587.0 + '@aws-sdk/region-config-resolver': 3.587.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-endpoints': 3.587.0 + '@aws-sdk/util-user-agent-browser': 3.577.0 + '@aws-sdk/util-user-agent-node': 3.587.0 + '@smithy/config-resolver': 3.0.1 + '@smithy/core': 2.2.0 + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-retry': 3.0.3 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.3 + '@smithy/util-defaults-mode-node': 3.0.3 + '@smithy/util-endpoints': 2.0.1 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + optional: true + + /@aws-sdk/core@3.592.0: + resolution: {integrity: sha512-gLPMXR/HXDP+9gXAt58t7gaMTvRts9i6Q7NMISpkGF54wehskl5WGrbdtHJFylrlJ5BQo3XVY6i661o+EuR1wg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/core': 2.2.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/signature-v4': 3.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + fast-xml-parser: 4.2.5 + tslib: 2.6.2 + optional: true + + /@aws-sdk/credential-provider-cognito-identity@3.592.0: + resolution: {integrity: sha512-uHiMPCkFhZOhlSfKgVqPhMdruiOuVkLUn07gQqvxHYhFKkEOPV+6BZbPKBwBTXr8TIREztQzCMPswa5pGk2zbQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/client-cognito-identity': 3.592.0 + '@aws-sdk/types': 3.577.0 + '@smithy/property-provider': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + optional: true + + /@aws-sdk/credential-provider-env@3.587.0: + resolution: {integrity: sha512-Hyg/5KFECIk2k5o8wnVEiniV86yVkhn5kzITUydmNGCkXdBFHMHRx6hleQ1bqwJHbBskyu8nbYamzcwymmGwmw==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/property-provider': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/credential-provider-http@3.587.0: + resolution: {integrity: sha512-Su1SRWVRCuR1e32oxX3C1V4c5hpPN20WYcRfdcr2wXwHqSvys5DrnmuCC+JoEnS/zt3adUJhPliTqpfKgSdMrA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/node-http-handler': 3.0.0 + '@smithy/property-provider': 3.1.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/util-stream': 3.0.1 + tslib: 2.6.2 + optional: true + + /@aws-sdk/credential-provider-ini@3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0): + resolution: {integrity: sha512-3kG6ngCIOPbLJZZ3RV+NsU7HVK6vX1+1DrPJKj9fVlPYn7IXsk8NAaUT5885yC7+jKizjv0cWLrLKvAJV5gfUA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + peerDependencies: + '@aws-sdk/client-sts': ^3.592.0 + dependencies: + '@aws-sdk/client-sts': 3.592.0 + '@aws-sdk/credential-provider-env': 3.587.0 + '@aws-sdk/credential-provider-http': 3.587.0 + '@aws-sdk/credential-provider-process': 3.587.0 + '@aws-sdk/credential-provider-sso': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0) + '@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/credential-provider-imds': 3.1.0 + '@smithy/property-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + optional: true + + /@aws-sdk/credential-provider-node@3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0): + resolution: {integrity: sha512-BguihBGTrEjVBQ07hm+ZsO29eNJaxwBwUZMftgGAm2XcMIEClNPfm5hydxu2BmA4ouIJQJ6nG8pNYghEumM+Aw==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/credential-provider-env': 3.587.0 + '@aws-sdk/credential-provider-http': 3.587.0 + '@aws-sdk/credential-provider-ini': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-process': 3.587.0 + '@aws-sdk/credential-provider-sso': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0) + '@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/credential-provider-imds': 3.1.0 + '@smithy/property-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + optional: true + + /@aws-sdk/credential-provider-process@3.587.0: + resolution: {integrity: sha512-V4xT3iCqkF8uL6QC4gqBJg/2asd/damswP1h9HCfqTllmPWzImS+8WD3VjgTLw5b0KbTy+ZdUhKc0wDnyzkzxg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/property-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/credential-provider-sso@3.592.0(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-fYFzAdDHKHvhtufPPtrLdSv8lO6GuW3em6n3erM5uFdpGytNpjXvr3XGokIsuXcNkETAY/Xihg+G9ksNE8WJxQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/client-sso': 3.592.0 + '@aws-sdk/token-providers': 3.587.0(@aws-sdk/client-sso-oidc@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/property-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + optional: true + + /@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.592.0): + resolution: {integrity: sha512-XqIx/I2PG7kyuw3WjAP9wKlxy8IvFJwB8asOFT1xPFoVfZYKIogjG9oLP5YiRtfvDkWIztHmg5MlVv3HdJDGRw==} + engines: {node: '>=16.0.0'} + requiresBuild: true + peerDependencies: + '@aws-sdk/client-sts': ^3.587.0 + dependencies: + '@aws-sdk/client-sts': 3.592.0 + '@aws-sdk/types': 3.577.0 + '@smithy/property-provider': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/credential-providers@3.592.0(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-fHAt001Aemiy9p8VtLKWiPQ36g1YgiLC1pm31W+WmKxU663dbt2yYTIAyVOB1nQC7HrVCOZEg2FU0TtuZt/wXQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/client-cognito-identity': 3.592.0 + '@aws-sdk/client-sso': 3.592.0 + '@aws-sdk/client-sts': 3.592.0 + '@aws-sdk/credential-provider-cognito-identity': 3.592.0 + '@aws-sdk/credential-provider-env': 3.587.0 + '@aws-sdk/credential-provider-http': 3.587.0 + '@aws-sdk/credential-provider-ini': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-process': 3.587.0 + '@aws-sdk/credential-provider-sso': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0) + '@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/credential-provider-imds': 3.1.0 + '@smithy/property-provider': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + optional: true + + /@aws-sdk/middleware-host-header@3.577.0: + resolution: {integrity: sha512-9ca5MJz455CODIVXs0/sWmJm7t3QO4EUa1zf8pE8grLpzf0J94bz/skDWm37Pli13T3WaAQBHCTiH2gUVfCsWg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/middleware-logger@3.577.0: + resolution: {integrity: sha512-aPFGpGjTZcJYk+24bg7jT4XdIp42mFXSuPt49lw5KygefLyJM/sB0bKKqPYYivW0rcuZ9brQ58eZUNthrzYAvg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/middleware-recursion-detection@3.577.0: + resolution: {integrity: sha512-pn3ZVEd2iobKJlR3H+bDilHjgRnNrQ6HMmK9ZzZw89Ckn3Dcbv48xOv4RJvu0aU8SDLl/SNCxppKjeLDTPGBNA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/middleware-user-agent@3.587.0: + resolution: {integrity: sha512-SyDomN+IOrygLucziG7/nOHkjUXES5oH5T7p8AboO8oakMQJdnudNXiYWTicQWO52R51U6CR27rcMPTGeMedYA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-endpoints': 3.587.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/region-config-resolver@3.587.0: + resolution: {integrity: sha512-93I7IPZtulZQoRK+O20IJ4a1syWwYPzoO2gc3v+/GNZflZPV3QJXuVbIm0pxBsu0n/mzKGUKqSOLPIaN098HcQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/types': 3.0.0 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-ULqhbnLy1hmJNRcukANBWJmum3BbjXnurLPSFXoGdV0llXYlG55SzIla2VYqdveQEEjmsBuTZdFvXAtNpmS5Zg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.587.0 + dependencies: + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/property-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/types@3.577.0: + resolution: {integrity: sha512-FT2JZES3wBKN/alfmhlo+3ZOq/XJ0C7QOZcDNrpKjB0kqYoKjhVKZ/Hx6ArR0czkKfHzBBEs6y40ebIHx2nSmA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/util-endpoints@3.587.0: + resolution: {integrity: sha512-8I1HG6Em8wQWqKcRW6m358mqebRVNpL8XrrEoT4In7xqkKkmYtHRNVYP6lcmiQh5pZ/c/FXu8dSchuFIWyEtqQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/types': 3.0.0 + '@smithy/util-endpoints': 2.0.1 + tslib: 2.6.2 + optional: true + + /@aws-sdk/util-locate-window@3.568.0: + resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + + /@aws-sdk/util-user-agent-browser@3.577.0: + resolution: {integrity: sha512-zEAzHgR6HWpZOH7xFgeJLc6/CzMcx4nxeQolZxVZoB5pPaJd3CjyRhZN0xXeZB0XIRCWmb4yJBgyiugXLNMkLA==} + requiresBuild: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/types': 3.0.0 + bowser: 2.11.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/util-user-agent-node@3.587.0: + resolution: {integrity: sha512-Pnl+DUe/bvnbEEDHP3iVJrOtE3HbFJBPgsD6vJ+ml/+IYk1Eq49jEG+EHZdNTPz3SDG0kbp2+7u41MKYJHR/iQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@aws-sdk/util-utf8-browser@3.259.0: + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -1919,6 +2451,13 @@ packages: - supports-color dev: true + /@mongodb-js/saslprep@1.1.7: + resolution: {integrity: sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==} + requiresBuild: true + dependencies: + sparse-bitfield: 3.0.3 + optional: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2002,56 +2541,458 @@ packages: peerDependencies: react: '>=16.8' dependencies: - '@juggle/resize-observer': 3.4.0 - '@react-hook/latest': 1.0.3(react@18.2.0) - '@react-hook/passive-layout-effect': 1.2.1(react@18.2.0) - react: 18.2.0 - dev: false + '@juggle/resize-observer': 3.4.0 + '@react-hook/latest': 1.0.3(react@18.2.0) + '@react-hook/passive-layout-effect': 1.2.1(react@18.2.0) + react: 18.2.0 + dev: false + + /@remix-run/router@1.15.2: + resolution: {integrity: sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==} + engines: {node: '>=14.0.0'} + dev: false + + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: true + + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: true + + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sindresorhus/is@4.6.0: + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + dev: true + + /@sindresorhus/merge-streams@2.3.0: + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + dev: true + + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + dependencies: + '@sinonjs/commons': 3.0.1 + dev: true + + /@smithy/abort-controller@3.0.0: + resolution: {integrity: sha512-p6GlFGBt9K4MYLu72YuJ523NVR4A8oHlC5M2JO6OmQqN8kAc/uh1JqLE+FizTokrSJGg0CSvC+BrsmGzKtsZKA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/config-resolver@3.0.1: + resolution: {integrity: sha512-hbkYJc20SBDz2qqLzttjI/EqXemtmWk0ooRznLsiXp3066KQRTvuKHa7U4jCZCJq6Dozqvy0R1/vNESC9inPJg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/node-config-provider': 3.1.0 + '@smithy/types': 3.0.0 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/core@2.2.0: + resolution: {integrity: sha512-ygLZSSKgt9bR8HAxR9mK+U5obvAJBr6zlQuhN5soYWx/amjDoQN4dTkydTypgKe6rIbUjTILyLU+W5XFwXr4kg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-retry': 3.0.3 + '@smithy/middleware-serde': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/util-middleware': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/credential-provider-imds@3.1.0: + resolution: {integrity: sha512-q4A4d38v8pYYmseu/jTS3Z5I3zXlEOe5Obi+EJreVKgSVyWUHOd7/yaVCinC60QG4MRyCs98tcxBH1IMC0bu7Q==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/node-config-provider': 3.1.0 + '@smithy/property-provider': 3.1.0 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/fetch-http-handler@3.0.1: + resolution: {integrity: sha512-uaH74i5BDj+rBwoQaXioKpI0SHBJFtOVwzrCpxZxphOW0ki5jhj7dXvDMYM2IJem8TpdFvS2iC08sjOblfFGFg==} + requiresBuild: true + dependencies: + '@smithy/protocol-http': 4.0.0 + '@smithy/querystring-builder': 3.0.0 + '@smithy/types': 3.0.0 + '@smithy/util-base64': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/hash-node@3.0.0: + resolution: {integrity: sha512-84qXstNemP3XS5jcof0el6+bDfjzuvhJPQTEfro3lgtbCtKgzPm3MgiS6ehXVPjeQ5+JS0HqmTz8f/RYfzHVxw==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/invalid-dependency@3.0.0: + resolution: {integrity: sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/is-array-buffer@3.0.0: + resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + + /@smithy/middleware-content-length@3.0.0: + resolution: {integrity: sha512-3C4s4d/iGobgCtk2tnWW6+zSTOBg1PRAm2vtWZLdriwTroFbbWNSr3lcyzHdrQHnEXYCC5K52EbpfodaIUY8sg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/protocol-http': 4.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/middleware-endpoint@3.0.1: + resolution: {integrity: sha512-lQ/UOdGD4KM5kLZiAl0q8Qy3dPbynvAXKAdXnYlrA1OpaUwr+neSsVokDZpY6ZVb5Yx8jnus29uv6XWpM9P4SQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/middleware-serde': 3.0.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-middleware': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/middleware-retry@3.0.3: + resolution: {integrity: sha512-Wve1qzJb83VEU/6q+/I0cQdAkDnuzELC6IvIBwDzUEiGpKqXgX1v10FUuZGbRS6Ov/P+HHthcAoHOJZQvZNAkA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/node-config-provider': 3.1.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/service-error-classification': 3.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + tslib: 2.6.2 + uuid: 9.0.1 + optional: true + + /@smithy/middleware-serde@3.0.0: + resolution: {integrity: sha512-I1vKG1foI+oPgG9r7IMY1S+xBnmAn1ISqployvqkwHoSb8VPsngHDTOgYGYBonuOKndaWRUGJZrKYYLB+Ane6w==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/middleware-stack@3.0.0: + resolution: {integrity: sha512-+H0jmyfAyHRFXm6wunskuNAqtj7yfmwFB6Fp37enytp2q047/Od9xetEaUbluyImOlGnGpaVGaVfjwawSr+i6Q==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/node-config-provider@3.1.0: + resolution: {integrity: sha512-ngfB8QItUfTFTfHMvKuc2g1W60V1urIgZHqD1JNFZC2tTWXahqf2XvKXqcBS7yZqR7GqkQQZy11y/lNOUWzq7Q==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/property-provider': 3.1.0 + '@smithy/shared-ini-file-loader': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/node-http-handler@3.0.0: + resolution: {integrity: sha512-3trD4r7NOMygwLbUJo4eodyQuypAWr7uvPnebNJ9a70dQhVn+US8j/lCnvoJS6BXfZeF7PkkkI0DemVJw+n+eQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/abort-controller': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/querystring-builder': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/property-provider@3.1.0: + resolution: {integrity: sha512-Tj3+oVhqdZgemjCiWjFlADfhvLF4C/uKDuKo7/tlEsRQ9+3emCreR2xndj970QSRSsiCEU8hZW3/8JQu+n5w4Q==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/protocol-http@4.0.0: + resolution: {integrity: sha512-qOQZOEI2XLWRWBO9AgIYuHuqjZ2csyr8/IlgFDHDNuIgLAMRx2Bl8ck5U5D6Vh9DPdoaVpuzwWMa0xcdL4O/AQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/querystring-builder@3.0.0: + resolution: {integrity: sha512-bW8Fi0NzyfkE0TmQphDXr1AmBDbK01cA4C1Z7ggwMAU5RDz5AAv/KmoRwzQAS0kxXNf/D2ALTEgwK0U2c4LtRg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + '@smithy/util-uri-escape': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/querystring-parser@3.0.0: + resolution: {integrity: sha512-UzHwthk0UEccV4dHzPySnBy34AWw3V9lIqUTxmozQ+wPDAO9csCWMfOLe7V9A2agNYy7xE+Pb0S6K/J23JSzfQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/service-error-classification@3.0.0: + resolution: {integrity: sha512-3BsBtOUt2Gsnc3X23ew+r2M71WwtpHfEDGhHYHSDg6q1t8FrWh15jT25DLajFV1H+PpxAJ6gqe9yYeRUsmSdFA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + optional: true + + /@smithy/shared-ini-file-loader@3.1.0: + resolution: {integrity: sha512-dAM7wSX0NR3qTNyGVN/nwwpEDzfV9T/3AN2eABExWmda5VqZKSsjlINqomO5hjQWGv+IIkoXfs3u2vGSNz8+Rg==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/signature-v4@3.0.0: + resolution: {integrity: sha512-kXFOkNX+BQHe2qnLxpMEaCRGap9J6tUGLzc3A9jdn+nD4JdMwCKTJ+zFwQ20GkY+mAXGatyTw3HcoUlR39HwmA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/is-array-buffer': 3.0.0 + '@smithy/types': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-uri-escape': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/smithy-client@3.1.1: + resolution: {integrity: sha512-tj4Ku7MpzZR8cmVuPcSbrLFVxmptWktmJMwST/uIEq4sarabEdF8CbmQdYB7uJ/X51Qq2EYwnRsoS7hdR4B7rA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-stack': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/types': 3.0.0 + '@smithy/util-stream': 3.0.1 + tslib: 2.6.2 + optional: true + + /@smithy/types@3.0.0: + resolution: {integrity: sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + + /@smithy/url-parser@3.0.0: + resolution: {integrity: sha512-2XLazFgUu+YOGHtWihB3FSLAfCUajVfNBXGGYjOaVKjLAuAxx3pSBY3hBgLzIgB17haf59gOG3imKqTy8mcrjw==} + requiresBuild: true + dependencies: + '@smithy/querystring-parser': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/util-base64@3.0.0: + resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/util-body-length-browser@3.0.0: + resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + + /@smithy/util-body-length-node@3.0.0: + resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true + + /@smithy/util-buffer-from@3.0.0: + resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/is-array-buffer': 3.0.0 + tslib: 2.6.2 + optional: true + + /@smithy/util-config-provider@3.0.0: + resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true - /@remix-run/router@1.15.2: - resolution: {integrity: sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==} - engines: {node: '>=14.0.0'} - dev: false + /@smithy/util-defaults-mode-browser@3.0.3: + resolution: {integrity: sha512-3DFON2bvXJAukJe+qFgPV/rorG7ZD3m4gjCXHD1V5z/tgKQp5MCTCLntrd686tX6tj8Uli3lefWXJudNg5WmCA==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + '@smithy/property-provider': 3.1.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + bowser: 2.11.0 + tslib: 2.6.2 + optional: true - /@sideway/address@4.1.5: - resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + /@smithy/util-defaults-mode-node@3.0.3: + resolution: {integrity: sha512-D0b8GJXecT00baoSQ3Iieu3k3mZ7GY8w1zmg8pdogYrGvWJeLcIclqk2gbkG4K0DaBGWrO6v6r20iwIFfDYrmA==} + engines: {node: '>= 10.0.0'} + requiresBuild: true dependencies: - '@hapi/hoek': 9.3.0 - dev: true + '@smithy/config-resolver': 3.0.1 + '@smithy/credential-provider-imds': 3.1.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/property-provider': 3.1.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true - /@sideway/formula@3.0.1: - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - dev: true + /@smithy/util-endpoints@2.0.1: + resolution: {integrity: sha512-ZRT0VCOnKlVohfoABMc8lWeQo/JEFuPWctfNRXgTHbyOVssMOLYFUNWukxxiHRGVAhV+n3c0kPW+zUqckjVPEA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/node-config-provider': 3.1.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true - /@sideway/pinpoint@2.0.0: - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - dev: true + /@smithy/util-hex-encoding@3.0.0: + resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + tslib: 2.6.2 + optional: true - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true + /@smithy/util-middleware@3.0.0: + resolution: {integrity: sha512-q5ITdOnV2pXHSVDnKWrwgSNTDBAMHLptFE07ua/5Ty5WJ11bvr0vk2a7agu7qRhrCFRQlno5u3CneU5EELK+DQ==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true - /@sindresorhus/is@4.6.0: - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - dev: true + /@smithy/util-retry@3.0.0: + resolution: {integrity: sha512-nK99bvJiziGv/UOKJlDvFF45F00WgPLKVIGUfAK+mDhzVN2hb/S33uW2Tlhg5PVBoqY7tDVqL0zmu4OxAHgo9g==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/service-error-classification': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + optional: true - /@sindresorhus/merge-streams@2.3.0: - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - dev: true + /@smithy/util-stream@3.0.1: + resolution: {integrity: sha512-7F7VNNhAsfMRA8I986YdOY5fE0/T1/ZjFF6OLsqkvQVNP3vZ/szYDfGCyphb7ioA09r32K/0qbSFfNFU68aSzA==} + engines: {node: '>=16.0.0'} + requiresBuild: true + dependencies: + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/node-http-handler': 3.0.0 + '@smithy/types': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + optional: true - /@sinonjs/commons@3.0.1: - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + /@smithy/util-uri-escape@3.0.0: + resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} + engines: {node: '>=16.0.0'} + requiresBuild: true dependencies: - type-detect: 4.0.8 - dev: true + tslib: 2.6.2 + optional: true - /@sinonjs/fake-timers@10.3.0: - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + /@smithy/util-utf8@3.0.0: + resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} + engines: {node: '>=16.0.0'} + requiresBuild: true dependencies: - '@sinonjs/commons': 3.0.1 - dev: true + '@smithy/util-buffer-from': 3.0.0 + tslib: 2.6.2 + optional: true /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} @@ -2319,7 +3260,6 @@ packages: resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==} dependencies: undici-types: 5.26.5 - dev: true /@types/plist@3.0.5: resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -2423,6 +3363,15 @@ packages: dev: true optional: true + /@types/webidl-conversions@7.0.3: + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + /@types/whatwg-url@8.2.2: + resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} + dependencies: + '@types/node': 20.11.24 + '@types/webidl-conversions': 7.0.3 + /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: @@ -2887,7 +3836,6 @@ packages: /array-flatten@3.0.0: resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} - dev: false /asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -2928,6 +3876,12 @@ packages: engines: {node: '>=0.12.0'} dev: true + /async-mutex@0.3.2: + resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==} + dependencies: + tslib: 2.6.2 + dev: true + /async@0.2.10: resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} dev: false @@ -2993,7 +3947,7 @@ packages: /axios@1.6.8: resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} dependencies: - follow-redirects: 1.15.6 + follow-redirects: 1.15.6(debug@4.3.4) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -3092,6 +4046,13 @@ packages: engines: {node: '>=8'} dev: true + /bl@2.2.1: + resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + dev: true + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -3158,7 +4119,6 @@ packages: type-is: 1.6.18 transitivePeerDependencies: - supports-color - dev: false /bonjour-service@1.2.1: resolution: {integrity: sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==} @@ -3177,6 +4137,11 @@ packages: dev: true optional: true + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + requiresBuild: true + optional: true + /bplist-parser@0.3.2: resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} engines: {node: '>= 5.10.0'} @@ -3280,6 +4245,17 @@ packages: node-int64: 0.4.0 dev: true + /bson@1.1.6: + resolution: {integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==} + engines: {node: '>=0.6.19'} + dev: true + + /bson@4.7.2: + resolution: {integrity: sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==} + engines: {node: '>=6.9.0'} + dependencies: + buffer: 5.7.1 + /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true @@ -3309,7 +4285,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true /builder-util-runtime@9.2.4: resolution: {integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==} @@ -3359,7 +4334,6 @@ packages: /bytes@3.1.1: resolution: {integrity: sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==} engines: {node: '>= 0.8'} - dev: false /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3630,6 +4604,11 @@ packages: engines: {node: '>= 6'} dev: true + /commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: true + /commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -3640,6 +4619,10 @@ packages: engines: {node: ^12.20.0 || >=14} dev: true + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: true + /compare-version@0.1.2: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} @@ -3746,7 +4729,6 @@ packages: /cookie@0.4.1: resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} engines: {node: '>= 0.6'} - dev: false /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} @@ -4043,7 +5025,6 @@ packages: optional: true dependencies: ms: 2.0.0 - dev: false /debug@3.2.7(supports-color@5.5.0): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -4163,6 +5144,11 @@ packages: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: true + /denque@1.5.1: + resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} + engines: {node: '>=0.10'} + dev: true + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -4181,7 +5167,6 @@ packages: /destroy@1.0.4: resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} - dev: false /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} @@ -4749,7 +5734,6 @@ packages: vary: 1.1.2 transitivePeerDependencies: - supports-color - dev: false /extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} @@ -4795,6 +5779,14 @@ packages: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} dev: true + /fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + requiresBuild: true + dependencies: + strnum: 1.0.5 + optional: true + /fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -4864,7 +5856,6 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - dev: false /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -4881,6 +5872,15 @@ packages: - supports-color dev: true + /find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + dev: true + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -4921,7 +5921,7 @@ packages: debug: optional: true - /follow-redirects@1.15.6: + /follow-redirects@1.15.6(debug@4.3.4): resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} engines: {node: '>=4.0'} peerDependencies: @@ -4929,6 +5929,8 @@ packages: peerDependenciesMeta: debug: optional: true + dependencies: + debug: 4.3.4 dev: true /for-each@0.3.3: @@ -5084,6 +6086,11 @@ packages: engines: {node: '>=8.0.0'} dev: true + /get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + dev: true + /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -5403,7 +6410,6 @@ packages: setprototypeof: 1.2.0 statuses: 1.5.0 toidentifier: 1.0.1 - dev: false /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} @@ -5455,7 +6461,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.6 + follow-redirects: 1.15.6(debug@4.3.4) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -5582,11 +6588,39 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /insta-mongo@0.0.6(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-OfihhSiUH3O9e3XUJpleKBLFWeIaBaXr7mpoX7wIZC3KKYF15aSNBRzpO0iUpLr7bf8YDSXT5EVvoofr3m7wAQ==} + hasBin: true + dependencies: + express: 5.0.0-beta.1 + minimist: 1.2.8 + mongodb: 4.17.2(@aws-sdk/client-sso-oidc@3.592.0) + mongodb-memory-server: 8.16.0(@aws-sdk/client-sso-oidc@3.592.0) + node-mongodb-fixtures: 3.2.9 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + - aws4 + - bson-ext + - kerberos + - mongodb-client-encryption + - mongodb-extjson + - snappy + - supports-color + dev: true + /interpret@3.1.1: resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} engines: {node: '>=10.13.0'} dev: true + /ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -6416,6 +7450,9 @@ packages: argparse: 2.0.1 dev: true + /jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + /jsdom@20.0.3(canvas@2.11.2): resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} engines: {node: '>=14'} @@ -6693,6 +7730,12 @@ packages: dev: true optional: true + /md5-file@5.0.0: + resolution: {integrity: sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} dependencies: @@ -6712,6 +7755,11 @@ packages: tslib: 2.6.2 dev: true + /memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + requiresBuild: true + optional: true + /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} @@ -6858,6 +7906,97 @@ packages: hasBin: true dev: true + /mongodb-connection-string-url@2.6.0: + resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==} + dependencies: + '@types/whatwg-url': 8.2.2 + whatwg-url: 11.0.0 + + /mongodb-memory-server-core@8.16.0(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-wyNo8yj6se7KH49hQmRtiwide7DnGINUGa1m84RyX1NU9DkCrTwbOV2VbPgd3+55DZfRup/DebU1M1zEv+3Rng==} + engines: {node: '>=12.22.0'} + dependencies: + async-mutex: 0.3.2 + camelcase: 6.3.0 + debug: 4.3.4 + find-cache-dir: 3.3.2 + follow-redirects: 1.15.6(debug@4.3.4) + get-port: 5.1.1 + https-proxy-agent: 5.0.1 + md5-file: 5.0.0 + mongodb: 4.17.2(@aws-sdk/client-sso-oidc@3.592.0) + new-find-package-json: 2.0.0 + semver: 7.6.0 + tar-stream: 2.2.0 + tslib: 2.6.2 + uuid: 9.0.1 + yauzl: 2.10.0 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + - supports-color + dev: true + + /mongodb-memory-server@8.16.0(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-oaeu2GZWycIysTj18b1gZ6d+CqWeQQZe5f8ml8Z1buaGAn3GcrGdbG5+0fseEO5ANQzcjA92qHhbsImgXeEmIQ==} + engines: {node: '>=12.22.0'} + requiresBuild: true + dependencies: + mongodb-memory-server-core: 8.16.0(@aws-sdk/client-sso-oidc@3.592.0) + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + - supports-color + dev: true + + /mongodb@3.7.4: + resolution: {integrity: sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==} + engines: {node: '>=4'} + peerDependencies: + aws4: '*' + bson-ext: '*' + kerberos: '*' + mongodb-client-encryption: '*' + mongodb-extjson: '*' + snappy: '*' + peerDependenciesMeta: + aws4: + optional: true + bson-ext: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + mongodb-extjson: + optional: true + snappy: + optional: true + dependencies: + bl: 2.2.1 + bson: 1.1.6 + denque: 1.5.1 + optional-require: 1.1.8 + safe-buffer: 5.2.1 + optionalDependencies: + saslprep: 1.0.3 + dev: true + + /mongodb@4.17.2(@aws-sdk/client-sso-oidc@3.592.0): + resolution: {integrity: sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==} + engines: {node: '>=12.9.0'} + dependencies: + bson: 4.7.2 + mongodb-connection-string-url: 2.6.0 + socks: 2.8.3 + optionalDependencies: + '@aws-sdk/credential-providers': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0) + '@mongodb-js/saslprep': 1.1.7 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -6925,6 +8064,15 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true + /new-find-package-json@2.0.0: + resolution: {integrity: sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==} + engines: {node: '>=12.22.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} dev: false @@ -6963,6 +8111,22 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true + /node-mongodb-fixtures@3.2.9: + resolution: {integrity: sha512-UCF7biEwA5kLB33kbhBViO4cC1Kmp4nm2w2LPfBOFMgVyflj+XITAAfgL331YLi8yfJCRUtHp/DbRDmfyjIjFw==} + hasBin: true + dependencies: + bluebird: 3.7.2 + commander: 6.2.1 + mongodb: 3.7.4 + transitivePeerDependencies: + - aws4 + - bson-ext + - kerberos + - mongodb-client-encryption + - mongodb-extjson + - snappy + dev: true + /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true @@ -7069,7 +8233,6 @@ packages: engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 - dev: false /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -7122,6 +8285,13 @@ packages: is-wsl: 2.2.0 dev: true + /optional-require@1.1.8: + resolution: {integrity: sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==} + engines: {node: '>=4'} + dependencies: + require-at: 1.0.6 + dev: true + /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -7284,7 +8454,6 @@ packages: /path-to-regexp@3.2.0: resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} - dev: false /path-type@5.0.0: resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} @@ -7585,7 +8754,6 @@ packages: /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true /pure-rand@6.0.4: resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} @@ -7612,7 +8780,6 @@ packages: /qs@6.9.6: resolution: {integrity: sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==} engines: {node: '>=0.6'} - dev: false /querystring@0.2.0: resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} @@ -7658,7 +8825,6 @@ packages: http-errors: 1.8.1 iconv-lite: 0.4.24 unpipe: 1.0.0 - dev: false /raw-body@2.5.2: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} @@ -7805,6 +8971,11 @@ packages: strip-ansi: 6.0.1 dev: true + /require-at@1.0.6: + resolution: {integrity: sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==} + engines: {node: '>=4'} + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -7936,7 +9107,6 @@ packages: path-to-regexp: 3.2.0 setprototypeof: 1.2.0 utils-merge: 1.0.1 - dev: false /run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} @@ -7970,6 +9140,15 @@ packages: truncate-utf8-bytes: 1.0.2 dev: true + /saslprep@1.0.3: + resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + sparse-bitfield: 3.0.3 + dev: true + optional: true + /sax@1.1.4: resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} dev: true @@ -8092,7 +9271,6 @@ packages: statuses: 1.5.0 transitivePeerDependencies: - supports-color - dev: false /serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} @@ -8146,7 +9324,6 @@ packages: send: 1.0.0-beta.1 transitivePeerDependencies: - supports-color - dev: false /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -8341,8 +9518,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} requiresBuild: true - dev: true - optional: true /sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} @@ -8352,6 +9527,13 @@ packages: websocket-driver: 0.7.4 dev: true + /socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -8381,6 +9563,13 @@ packages: engines: {node: '>= 8'} dev: true + /sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + requiresBuild: true + dependencies: + memory-pager: 1.5.0 + optional: true + /spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} dev: true @@ -8423,8 +9612,6 @@ packages: /sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} requiresBuild: true - dev: true - optional: true /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} @@ -8523,6 +9710,11 @@ packages: engines: {node: '>=8'} dev: true + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + requiresBuild: true + optional: true + /style-loader@3.3.4(webpack@5.90.3): resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} engines: {node: '>= 12.13.0'} @@ -8808,7 +10000,6 @@ packages: engines: {node: '>=12'} dependencies: punycode: 2.3.1 - dev: true /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} @@ -9036,6 +10227,11 @@ packages: strip-json-comments: 2.0.1 dev: true + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + requiresBuild: true + optional: true + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -9085,7 +10281,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true /unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} @@ -9182,6 +10377,10 @@ packages: requiresBuild: true dev: true + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true @@ -9280,7 +10479,6 @@ packages: /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - dev: true /webpack-cli@5.1.4(webpack-dev-server@5.0.2)(webpack@5.90.3): resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} @@ -9472,7 +10670,6 @@ packages: dependencies: tr46: 3.0.0 webidl-conversions: 7.0.0 - dev: true /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} diff --git a/testbed/package.json b/testbed/package.json index b0d4702..84a1ca3 100644 --- a/testbed/package.json +++ b/testbed/package.json @@ -37,7 +37,6 @@ "ts-loader": "^9.5.1", "typescript": "^4.9.5", "user-interface": "workspace:*", - "database": "workspace:*", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.2" diff --git a/testbed/src/tests/test-indexeddb.tsx b/testbed/src/tests/test-indexeddb.tsx index 1c59219..17572cf 100644 --- a/testbed/src/tests/test-indexeddb.tsx +++ b/testbed/src/tests/test-indexeddb.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react"; -import { IndexeddbDatabases, indexeddb, PersistentQueue, IAsset, IPage } from "database"; +import { IndexeddbDatabases, indexeddb, PersistentQueue } from "user-interface"; // // Checks for equality between two arrays. @@ -127,15 +127,8 @@ export function TestIndexeddb() { console.log(`!! All recoreds in test collection:`); const metadataCollection = databases.database("test-collection").collection("metadata"); - let next: string | undefined = undefined; - while (true) { - const page: IPage = await metadataCollection.getAll(1000, next) - console.log(page.records); - next = page.next; - if (!next) { - break; - } - } + const records = await metadataCollection.getAll(); + console.log(records); await databases.shutdown(); } diff --git a/tools/upload/package.json b/tools/upload/package.json index 656c598..12718e9 100644 --- a/tools/upload/package.json +++ b/tools/upload/package.json @@ -15,13 +15,13 @@ }, "dependencies": { "axios": "^0.27.2", - "database": "workspace:*", "dayjs": "^1.11.7", "exif-parser": "^0.1.12", "ffmpeg-ffprobe-static": "^6.1.1", "fluent-ffmpeg": "^2.1.3", "sharp": "^0.33.4", - "user-interface": "workspace:*" + "user-interface": "workspace:*", + "defs": "workspace:*" }, "devDependencies": { "@types/node": "^20.3.1", diff --git a/tools/upload/src/index.ts b/tools/upload/src/index.ts index 27fd19f..5ea23e9 100644 --- a/tools/upload/src/index.ts +++ b/tools/upload/src/index.ts @@ -1,13 +1,13 @@ import * as config from "./config"; import { findAssets } from "./scan"; -import { IAsset, IDatabaseOp, uuid } from "database"; import axios from "axios"; import fs from "fs"; import path from "path"; import sharp from "sharp"; import os from "os"; import dayjs from "dayjs"; -import { IResolution, convertExifCoordinates, isLocationInRange, retry, reverseGeocode } from "user-interface"; +import { IAsset, IDatabaseOp } from "defs"; +import { IResolution, convertExifCoordinates, isLocationInRange, retry, reverseGeocode, uuid } from "user-interface"; const exifParser = require("exif-parser"); const ffmpeg = require('fluent-ffmpeg'); const ffmpegPaths = require('ffmpeg-ffprobe-static'); @@ -166,6 +166,7 @@ async function uploadAsset(filePath: string, contentType: string): Promise // await addAsset(config.uploadCollectionId, { _id: assetId, + setId: config.uploadCollectionId, width: resolution.width, height: resolution.height, origFileName: path.basename(filePath), @@ -428,13 +429,13 @@ async function uploadAssetData(collectionId: string, assetId: string, assetType: // // Adds an asset to the start of the gallery. // -async function addAsset(collectionId: string, asset: IAsset): Promise { +async function addAsset(setId: string, asset: IAsset): Promise { // // Add the asset to the database. // const ops: IDatabaseOp[] = [ { - databaseName: collectionId, + setId, collectionName: "metadata", recordId: asset._id, op: { @@ -443,7 +444,7 @@ async function addAsset(collectionId: string, asset: IAsset): Promise { }, }, { - databaseName: collectionId, + setId, collectionName: "hashes", recordId: asset.hash, op: {