Skip to content

Commit

Permalink
feat: add contributable domains endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Melisa Anabella Rossi committed May 13, 2024
1 parent b656750 commit a0b7dc7
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 1 deletion.
18 changes: 17 additions & 1 deletion src/adapters/worlds-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,21 @@ export async function createWorldsManagerComponent({
await database.query(sql)
}

async function getContributableDomains(address: string): Promise<{ domains: string[]; count: number }> {
const result = await database.query<Record<'name', string>>(SQL`
SELECT DISTINCT name
FROM (
SELECT *
FROM worlds w, json_each_text(w.permissions) AS perm(permission, permissionValue)
WHERE permission = ANY(ARRAY['deployment', 'streaming'])
) AS wp
WHERE EXISTS (
SELECT 1 FROM json_array_elements_text(wp.permissionValue::json -> 'wallets') as wallet WHERE LOWER(wallet) = LOWER(${address})
)
`)
return { domains: result.rows.map(({ name }) => name), count: result.rowCount }
}

return {
getRawWorldRecords,
getDeployedWorldCount,
Expand All @@ -190,6 +205,7 @@ export async function createWorldsManagerComponent({
deployScene,
storePermissions,
permissionCheckerForWorld,
undeploy
undeploy,
getContributableDomains
}
}
15 changes: 15 additions & 0 deletions src/controllers/handlers/contributor-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DecentralandSignatureContext } from '@dcl/platform-crypto-middleware'
import { HandlerContextWithPath } from '../../types'
import { IHttpServerComponent } from '@well-known-components/interfaces'

export async function getContributableDomainsHandler(
ctx: HandlerContextWithPath<'worldsManager', '/world/contribute'> & DecentralandSignatureContext<any>
): Promise<IHttpServerComponent.IResponse> {
const { worldsManager } = ctx.components
const address = ctx.verification!.auth
const body = await worldsManager.getContributableDomains(address)
return {
status: 200,
body
}
}
3 changes: 3 additions & 0 deletions src/controllers/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { undeployEntity } from './handlers/undeploy-entity-handler'
import { bearerTokenMiddleware, errorHandler } from '@dcl/platform-server-commons'
import { reprocessABHandler } from './handlers/reprocess-ab-handler'
import { garbageCollectionHandler } from './handlers/garbage-collection'
import { getContributableDomainsHandler } from './handlers/contributor-handler'

export async function setupRouter(globalContext: GlobalContext): Promise<Router<GlobalContext>> {
const router = new Router<GlobalContext>()
Expand All @@ -48,6 +49,8 @@ export async function setupRouter(globalContext: GlobalContext): Promise<Router<
router.head('/contents/:hashId', headContentFile)
router.get('/contents/:hashId', getContentFile)

router.get('/world/contribute', signedFetchMiddleware, getContributableDomainsHandler)

router.get('/world/:world_name/permissions', getPermissionsHandler)
router.post('/world/:world_name/permissions/:permission_name', signedFetchMiddleware, postPermissionsHandler)
router.put(
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export type IWorldsManager = {
storePermissions(worldName: string, permissions: Permissions): Promise<void>
permissionCheckerForWorld(worldName: string): Promise<IPermissionChecker>
undeploy(worldName: string): Promise<void>
getContributableDomains(address: string): Promise<{ domains: string[]; count: number }>
}

export type IPermissionsManager = {
Expand Down
95 changes: 95 additions & 0 deletions test/integration/contributor-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Authenticator } from '@dcl/crypto'
import { test } from '../components'
import { IWorldCreator, IWorldsManager, Permissions, PermissionType } from '../../src/types'
import { defaultPermissions } from '../../src/logic/permissions-checker'
import { Identity, getIdentity, getAuthHeaders } from '../utils'

test('ContributorHandler', function ({ components }) {
let worldCreator: IWorldCreator
let worldsManager: IWorldsManager
let identity: Identity
let worldName: string

function makeRequest(path: string, identity: Identity) {
return components.localFetch.fetch(path, {
method: 'GET',
headers: {
...getAuthHeaders(
'get',
path,
{
origin: 'https://play.decentraland.org',
intent: 'dcl:explorer:comms-handshake',
signer: 'dcl:explorer',
isGuest: 'false'
},
(payload) =>
Authenticator.signPayload(
{
ephemeralIdentity: identity.ephemeralIdentity,
expiration: new Date(),
authChain: identity.authChain.authChain
},
payload
)
)
}
})
}

beforeEach(async () => {
worldCreator = components.worldCreator
worldsManager = components.worldsManager

identity = await getIdentity()

const created = await worldCreator.createWorldWithScene()
worldName = created.worldName
})

describe('/world/contribute', () => {
describe("when user doesn't have contributor permission to any world", () => {
it('returns an empty list', async () => {
const r = await makeRequest('/world/contribute', identity)

expect(r.status).toBe(200)
expect(await r.json()).toMatchObject({ domains: [], count: 0 })
})
})

describe('when user has streamer permission to world', () => {
it('returns list of domains', async () => {
const permissions: Permissions = {
...defaultPermissions(),
streaming: {
type: PermissionType.AllowList,
wallets: [identity.realAccount.address.toLowerCase()]
}
}
await worldsManager.storePermissions(worldName, permissions)
const r = await makeRequest('/world/contribute', identity)

expect(r.status).toBe(200)
expect(await r.json()).toMatchObject({ domains: [worldName], count: 1 })
})
})

describe('when user has deployment permission to world', () => {
it('returns list of domains', async () => {
const permissions: Permissions = {
...defaultPermissions(),
deployment: {
type: PermissionType.AllowList,
wallets: [identity.realAccount.address.toLocaleLowerCase()]
}
}

await worldsManager.storePermissions(worldName, permissions)
const r = await makeRequest('/world/contribute', identity)

expect(r.status).toBe(200)
expect(await r.json()).toMatchObject({ domains: [worldName], count: 1 })
})
})
})
})
15 changes: 15 additions & 0 deletions test/mocks/worlds-manager-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,22 @@ export async function createWorldsManagerMockComponent({
await storage.delete([`name-${worldName.toLowerCase()}`])
}

async function getContributableDomains(address: string): Promise<{ domains: string[]; count: number }> {
const domains = []
for await (const name of storage.allFileIds('name-')) {
const metadata = await getMetadataForWorld(name)
if (
metadata.permissions.deployment.wallets.includes(address) ||
('wallets' in metadata.permissions.streaming && metadata.permissions.streaming.wallets.includes(address))
) {
domains.push(name)
}
}
return { domains, count: domains.length }
}

return {
getContributableDomains,
getRawWorldRecords,
getDeployedWorldCount,
getDeployedWorldEntities,
Expand Down

0 comments on commit a0b7dc7

Please sign in to comment.