diff --git a/src/adapters/worlds-manager.ts b/src/adapters/worlds-manager.ts index 749d38bc..2c2d3a6b 100644 --- a/src/adapters/worlds-manager.ts +++ b/src/adapters/worlds-manager.ts @@ -86,7 +86,19 @@ export async function createWorldsManagerComponent({ const fileInfos = await storage.fileInfoMultiple(scene.content?.map((c) => c.hash) || []) const size = scene.content?.reduce((acc, c) => acc + (fileInfos.get(c.hash)?.size || 0), 0) || 0 - const sqlWorld = SQL` + const existingScenes = await getDeployedScenesForWorld(worldName) + const collidingScenes = await findCollisions(scene, existingScenes) + + try { + await database.query(SQL`BEGIN`) + await database.query( + SQL`DELETE + FROM scenes + WHERE world_name = ${worldName.toLowerCase()} + AND entity_id = ANY (${collidingScenes.map((s) => s.entity_id)})` + ) + + const sqlWorld = SQL` INSERT INTO worlds (name, owner, permissions, created_at, updated_at) VALUES (${worldName.toLowerCase()}, ${owner?.toLowerCase()}, @@ -97,9 +109,9 @@ export async function createWorldsManagerComponent({ DO UPDATE SET owner = ${owner?.toLowerCase()}, updated_at = ${new Date()} ` - await database.query(sqlWorld) + await database.query(sqlWorld) - const sqlScene = SQL` + const sqlScene = SQL` INSERT INTO scenes (world_name, entity_id, deployer, deployment_auth_chain, entity, size, created_at, updated_at) VALUES (${worldName.toLowerCase()}, ${scene.id}, @@ -117,7 +129,13 @@ export async function createWorldsManagerComponent({ size = ${size}, updated_at = ${new Date()} ` - await database.query(sqlScene) + await database.query(sqlScene) + await database.query(SQL`COMMIT`) + } catch (error: any) { + logger.warn(`Error deploying scene: ${error.message}`) + await database.query(SQL`ROLLBACK`) + throw error + } } async function storePermissions(worldName: string, permissions: Permissions): Promise { @@ -174,7 +192,7 @@ export async function createWorldsManagerComponent({ } const result = await database.query>( - SQL`SELECT entity_id, entity FROM scenes WHERE world_name = ${worldName.toLowerCase()} ORDER BY name` + SQL`SELECT entity_id, entity FROM scenes WHERE world_name = ${worldName.toLowerCase()} ORDER BY world_name` ) if (result.rowCount === 0) { @@ -193,6 +211,23 @@ export async function createWorldsManagerComponent({ await database.query(SQL`DELETE FROM scenes WHERE world_name = ${worldName.toLowerCase()}`) } + async function getDeployedScenesForWorld(worldName: string): Promise { + const result = await database.query( + SQL`SELECT * FROM scenes WHERE world_name = ${worldName.toLowerCase()} ORDER BY created_at DESC` + ) + + return result.rows + } + + async function findCollisions(entity: Entity, previousScenes: SceneRecord[]): Promise { + const newParcels = new Set(entity.pointers) + return previousScenes.filter( + (row) => + newParcels.has(row.entity.metadata.scene.base) || + row.entity.metadata.scene.parcels.some((parcel: string) => newParcels.has(parcel)) + ) + } + return { getRawSceneRecords, getDeployedWorldCount, diff --git a/src/logic/world-runtime-metadata-utils.ts b/src/logic/world-runtime-metadata-utils.ts index b4bb43be..2033fe28 100644 --- a/src/logic/world-runtime-metadata-utils.ts +++ b/src/logic/world-runtime-metadata-utils.ts @@ -52,7 +52,6 @@ export function extractWorldRuntimeMetadata(worldName: string, entities: Entity[ name: worldName, entityIds: entities.map(({ id }) => id) } as WorldRuntimeMetadata - // migrateConfiguration(worldName, entity.metadata?.worldConfiguration) function resolveFilename(entity: Entity, filename: string | undefined): string | undefined { if (filename) { diff --git a/test/integration/deploy.spec.ts b/test/integration/deploy.spec.ts index a00983e5..2d3ab9d1 100644 --- a/test/integration/deploy.spec.ts +++ b/test/integration/deploy.spec.ts @@ -100,7 +100,6 @@ test('deployment works', function ({ components, stubComponents }) { const stored = await worldsManager.getMetadataForWorld(worldName) expect(stored).toMatchObject({ - entityId, runtimeMetadata: { name: worldName, entityIds: [entityId], @@ -170,7 +169,6 @@ test('deployment works', function ({ components, stubComponents }) { const stored = await worldsManager.getMetadataForWorld(worldName) expect(stored).toMatchObject({ - entityId, runtimeMetadata: { entityIds: [entityId], minimapVisible: false, @@ -228,7 +226,6 @@ test('deployment works', function ({ components, stubComponents }) { const stored = await worldsManager.getMetadataForWorld(worldName) expect(stored).toMatchObject({ - entityId, runtimeMetadata: { name: worldName, entityIds: [entityId], diff --git a/test/mocks/worlds-manager-mock.ts b/test/mocks/worlds-manager-mock.ts index 97b32621..fe5386c9 100644 --- a/test/mocks/worlds-manager-mock.ts +++ b/test/mocks/worlds-manager-mock.ts @@ -3,8 +3,8 @@ import { IPermissionChecker, IWorldsManager, Permissions, - WorldMetadata, - WorldRecord + SceneRecord, + WorldMetadata } from '../../src/types' import { bufferToStream, streamToBuffer } from '@dcl/catalyst-storage' import { Entity } from '@dcl/schemas' @@ -15,38 +15,48 @@ import { createPermissionChecker, defaultPermissions } from '../../src/logic/per export async function createWorldsManagerMockComponent({ storage }: Pick): Promise { - async function getRawWorldRecords(): Promise { - const worlds: WorldRecord[] = [] + async function getRawSceneRecords(): Promise { + const scenes: SceneRecord[] = [] for await (const key of storage.allFileIds('name-')) { - const entity = await getEntityForWorld(key.substring(5)) - if (entity) { - const content = await storage.retrieve(`${entity.id}.auth`) - const authChain = JSON.parse((await streamToBuffer(await content?.asStream())).toString()) - worlds.push({ - name: entity.metadata.worldConfiguration.name, - deployer: authChain[0].payload, - entity_id: entity.id, + const metadata = await getMetadataForWorld(key) + if (!metadata || metadata.runtimeMetadata.entityIds.length === 0) { + continue + } + for (const entityId of metadata.runtimeMetadata.entityIds) { + const content = await storage.retrieve(entityId) + if (!content) { + continue + } + + const json = JSON.parse((await streamToBuffer(await content.asStream())).toString()) + const authChainText = await storage.retrieve(`${entityId}.auth`) + const authChain = JSON.parse((await streamToBuffer(await authChainText?.asStream())).toString()) + + scenes.push({ + world_name: key, + entity_id: entityId, + entity: json, deployment_auth_chain: authChain, - entity: entity.metadata, - created_at: new Date(1706019701900), - updated_at: new Date(1706019701900), - permissions: { ...defaultPermissions() }, - size: 0n, - owner: authChain[0].payload, - blocked_since: null + deployer: authChain[0].payload, + size: 0n }) } } - return worlds + return scenes } async function getEntityForWorld(worldName: string): Promise { const metadata = await getMetadataForWorld(worldName) - if (!metadata || !metadata.entityId) { + if ( + !metadata || + !metadata.runtimeMetadata || + !metadata.runtimeMetadata.entityIds || + metadata.runtimeMetadata.entityIds.length === 0 + ) { return undefined } - const content = await storage.retrieve(metadata.entityId) + const content = await storage.retrieve(metadata.runtimeMetadata.entityIds[0]) if (!content) { return undefined } @@ -55,7 +65,7 @@ export async function createWorldsManagerMockComponent({ return { ...json, - id: metadata.entityId + id: metadata.runtimeMetadata.entityIds[0] } } @@ -80,8 +90,7 @@ export async function createWorldsManagerMockComponent({ async function deployScene(worldName: string, scene: Entity): Promise { await storeWorldMetadata(worldName, { - entityId: scene.id, - runtimeMetadata: extractWorldRuntimeMetadata(worldName, scene) + runtimeMetadata: extractWorldRuntimeMetadata(worldName, [scene]) }) } @@ -122,7 +131,7 @@ export async function createWorldsManagerMockComponent({ } return { - getRawWorldRecords, + getRawSceneRecords, getDeployedWorldCount, getDeployedWorldEntities, getMetadataForWorld, diff --git a/test/unit/world-indexer.spec.ts b/test/unit/world-indexer.spec.ts index bce832e0..bd7f6c2d 100644 --- a/test/unit/world-indexer.spec.ts +++ b/test/unit/world-indexer.spec.ts @@ -20,7 +20,11 @@ describe('All data from worlds', function () { 'name-world-name.dcl.eth', bufferToStream( Buffer.from( - stringToUtf8Bytes(JSON.stringify({ entityId: 'bafkreielwj3ki46munydwn4ayazdvmjln76khmz2xyaf5v6dkmo6yoebbi' })) + stringToUtf8Bytes( + JSON.stringify({ + runtimeMetadata: { entityIds: ['bafkreielwj3ki46munydwn4ayazdvmjln76khmz2xyaf5v6dkmo6yoebbi'] } + }) + ) ) ) ) @@ -28,7 +32,11 @@ describe('All data from worlds', function () { 'name-another-world-name.dcl.eth', bufferToStream( Buffer.from( - stringToUtf8Bytes(JSON.stringify({ entityId: 'bafkreic6ix3pdwf7g24reg4ktlyjpmtbqbc2nq4zocupkmul37am4vlt6y' })) + stringToUtf8Bytes( + JSON.stringify({ + runtimeMetadata: { entityIds: ['bafkreic6ix3pdwf7g24reg4ktlyjpmtbqbc2nq4zocupkmul37am4vlt6y'] } + }) + ) ) ) )