From 7478fa7da8cc503506cfeb487c4c59fc7a37ec2a Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 11:23:58 -0300 Subject: [PATCH 01/11] chore: Add API documentation --- docs/openapi.yaml | 2351 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2351 insertions(+) create mode 100644 docs/openapi.yaml diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 00000000..8be553ad --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,2351 @@ +openapi: 3.0.3 +info: + title: Builder Server API + version: 2.10.0 + description: | + ## Overview + + The Builder Server API provides backend services for the Decentraland Builder application. + It enables management of scene projects, wearable/emote collections, asset packs, and more. + + ## Authentication + + Most endpoints require authentication using Decentraland's Signed Fetch mechanism (ADR-44). + Authentication is provided via the `X-Identity-Auth-Chain-*` headers. + + ## Base URL + + The API version prefix is configurable via the `API_VERSION` environment variable (default: `v1`). + All endpoints are prefixed with `/{API_VERSION}/`. + + contact: + name: Decentraland Foundation + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + +servers: + - url: http://localhost:5000/v1 + description: Local development server + +tags: + - name: App + description: Application info and health check + - name: Projects + description: Scene project management + - name: Pools + description: Public scene pools for community sharing + - name: Pool Groups + description: Pool categorization and grouping + - name: Pool Likes + description: Pool like management + - name: Collections + description: Wearable and emote collection management + - name: Items + description: Wearable and emote item management + - name: Curations + description: Collection and item curation workflow + - name: Committee + description: Committee member operations + - name: Third Party + description: Third-party collection management + - name: Rarities + description: Item rarity definitions + - name: Forum + description: Discourse forum integration + - name: Asset Packs + description: Asset pack management + - name: Assets + description: Individual 3D asset management + - name: Manifest + description: Project manifest operations + - name: Deployments + description: Scene deployment tracking + - name: Storage + description: S3 storage proxy endpoints + - name: Share + description: Share link generation + - name: Analytics + description: Analytics endpoints + - name: NFT + description: NFT-related operations + - name: LAND + description: LAND parcel operations + - name: Newsletter + description: Newsletter subscription management + +security: + - SignedFetch: [] + +paths: + # App endpoints + /info: + get: + tags: [App] + summary: Get server version + description: Returns the current server version + security: [] + responses: + '200': + description: Server version information + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: object + properties: + version: + type: string + + # Project endpoints + /templates: + get: + tags: [Projects] + summary: Get all project templates + description: Returns all public project templates + security: [] + responses: + '200': + description: List of project templates + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectListResponse' + + /projects: + get: + tags: [Projects] + summary: Get all projects + description: Returns all projects for the authenticated user + responses: + '200': + description: List of projects + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectListResponse' + '401': + $ref: '#/components/responses/Unauthorized' + + /projects/{id}: + get: + tags: [Projects] + summary: Get a project + description: Returns a specific project by ID + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: Project details + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + put: + tags: [Projects] + summary: Create or update a project + description: Creates a new project or updates an existing one + parameters: + - $ref: '#/components/parameters/ProjectId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectInput' + responses: + '200': + description: Project created/updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + delete: + tags: [Projects] + summary: Delete a project + description: Soft deletes a project + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: Project deleted successfully + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + + /projects/{id}/public: + get: + tags: [Projects] + summary: Get a public project + description: Returns a public project by ID (no authentication required) + security: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: Public project details + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectResponse' + '404': + $ref: '#/components/responses/NotFound' + + /projects/{id}/media: + post: + tags: [Projects] + summary: Upload project media + description: Uploads media files (thumbnails, previews) for a project + parameters: + - $ref: '#/components/parameters/ProjectId' + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + thumbnail: + type: string + format: binary + preview: + type: string + format: binary + north: + type: string + format: binary + east: + type: string + format: binary + south: + type: string + format: binary + west: + type: string + format: binary + responses: + '200': + description: Media uploaded successfully + '401': + $ref: '#/components/responses/Unauthorized' + + /projects/{id}/media/{filename}: + get: + tags: [Projects] + summary: Get project media file + description: Redirects to the S3 URL for the media file + security: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + - name: filename + in: path + required: true + schema: + type: string + responses: + '301': + description: Redirect to S3 URL + '404': + $ref: '#/components/responses/NotFound' + + /projects/{id}/contents/{content}: + get: + tags: [Projects] + summary: Get project content + description: Returns project content by hash + security: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + - name: content + in: path + required: true + schema: + type: string + responses: + '301': + description: Redirect to content URL + '404': + $ref: '#/components/responses/NotFound' + + /projects/{id}/about: + get: + tags: [Projects] + summary: Get project preview info + description: Returns realm configuration for project preview + security: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: Preview configuration + content: + application/json: + schema: + type: object + + /projects/{id}/crdt: + get: + tags: [Projects] + summary: Get project CRDT + description: Returns the CRDT file for the project + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '302': + description: Redirect to CRDT file + put: + tags: [Projects] + summary: Upload project CRDT + description: Uploads the CRDT file for a project + parameters: + - $ref: '#/components/parameters/ProjectId' + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + responses: + '200': + description: CRDT uploaded successfully + + /projects/{coords}/coords: + delete: + tags: [Projects] + summary: Remove coords from projects + description: Removes creation coordinates from all projects with the given coords + parameters: + - name: coords + in: path + required: true + schema: + type: string + responses: + '200': + description: Coordinates removed successfully + + # Pool endpoints + /pools: + get: + tags: [Pools] + summary: Get all pools + description: Returns all public scene pools with filtering and pagination + security: [] + parameters: + - name: eth_address + in: query + schema: + type: string + - name: group + in: query + schema: + type: string + - $ref: '#/components/parameters/SortBy' + - $ref: '#/components/parameters/SortOrder' + - $ref: '#/components/parameters/Limit' + - $ref: '#/components/parameters/Offset' + responses: + '200': + description: List of pools + content: + application/json: + schema: + $ref: '#/components/schemas/PoolListResponse' + + /projects/{id}/pool: + get: + tags: [Pools] + summary: Get a pool + description: Returns a pool by project ID + security: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: Pool details + content: + application/json: + schema: + $ref: '#/components/schemas/PoolResponse' + put: + tags: [Pools] + summary: Create or update a pool + description: Creates a pool from a project or updates an existing pool + parameters: + - $ref: '#/components/parameters/ProjectId' + requestBody: + content: + application/json: + schema: + type: object + properties: + groups: + type: array + items: + type: string + format: uuid + responses: + '200': + description: Pool created/updated successfully + + # Pool Group endpoints + /pool-groups: + get: + tags: [Pool Groups] + summary: Get all pool groups + description: Returns all pool groups + security: [] + responses: + '200': + description: List of pool groups + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/PoolGroup' + + # Pool Like endpoints + /pool-likes/{poolId}: + put: + tags: [Pool Likes] + summary: Like a pool + description: Adds a like to a pool + parameters: + - name: poolId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Pool liked successfully + delete: + tags: [Pool Likes] + summary: Unlike a pool + description: Removes a like from a pool + parameters: + - name: poolId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Pool unliked successfully + + # Collection endpoints + /collections: + get: + tags: [Collections] + summary: Get all collections + description: Returns collections with filtering and pagination (committee members only for full access) + parameters: + - name: q + in: query + schema: + type: string + description: Search query + - name: assignee + in: query + schema: + type: string + - name: status + in: query + schema: + $ref: '#/components/schemas/CurationStatus' + - name: type + in: query + schema: + type: string + enum: [standard, third_party] + - name: is_published + in: query + schema: + type: string + enum: ['true', 'false'] + - name: sort + in: query + schema: + type: string + enum: + [ + MOST_RELEVANT, + CREATED_AT_DESC, + CREATED_AT_ASC, + NAME_DESC, + NAME_ASC, + UPDATED_AT_DESC, + UPDATED_AT_ASC, + ] + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/Limit' + responses: + '200': + description: List of collections + content: + application/json: + schema: + $ref: '#/components/schemas/CollectionListResponse' + + /{address}/collections: + get: + tags: [Collections] + summary: Get collections by address + description: Returns collections for a specific address + parameters: + - name: address + in: path + required: true + schema: + type: string + - name: is_published + in: query + schema: + type: string + - name: sort + in: query + schema: + type: string + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/Limit' + responses: + '200': + description: List of collections + content: + application/json: + schema: + $ref: '#/components/schemas/CollectionListResponse' + + /collections/{id}: + get: + tags: [Collections] + summary: Get a collection + description: Returns a collection by ID + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Collection details + content: + application/json: + schema: + $ref: '#/components/schemas/CollectionResponse' + '404': + $ref: '#/components/responses/NotFound' + put: + tags: [Collections] + summary: Create or update a collection + description: Creates a new collection or updates an existing one + parameters: + - $ref: '#/components/parameters/CollectionId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CollectionInput' + responses: + '200': + description: Collection created/updated successfully + '401': + $ref: '#/components/responses/Unauthorized' + '409': + $ref: '#/components/responses/Conflict' + delete: + tags: [Collections] + summary: Delete a collection + description: Deletes a collection + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Collection deleted successfully + '409': + $ref: '#/components/responses/Conflict' + + /collections/{id}/publish: + post: + tags: [Collections] + summary: Publish a collection + description: Publishes a collection to the blockchain + parameters: + - $ref: '#/components/parameters/CollectionId' + requestBody: + content: + application/json: + schema: + type: object + properties: + itemIds: + type: array + items: + type: string + cheque: + $ref: '#/components/schemas/Cheque' + responses: + '200': + description: Collection published successfully + '409': + $ref: '#/components/responses/Conflict' + + /collections/{id}/lock: + post: + tags: [Collections] + summary: Lock a collection + description: Locks a collection until publication + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Collection locked successfully + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: string + format: date-time + + /collections/{id}/tos: + post: + tags: [Collections] + summary: Save Terms of Service + description: Records Terms of Service acceptance for collection publication + parameters: + - $ref: '#/components/parameters/CollectionId' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + properties: + email: + type: string + format: email + event: + type: string + enum: [publish_collection_tos, publish_third_party_items_tos] + hashes: + type: array + items: + type: string + responses: + '200': + description: TOS recorded successfully + + /collections/{id}/approvalData: + get: + tags: [Collections] + summary: Get approval data + description: Returns data needed to approve a third-party collection + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Approval data + content: + application/json: + schema: + $ref: '#/components/schemas/ItemApprovalData' + + /addresses: + get: + tags: [Collections] + summary: Get collection addresses + description: Returns collection contract addresses with filtering + security: [] + parameters: + - name: status + in: query + schema: + $ref: '#/components/schemas/CurationStatus' + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/Limit' + responses: + '200': + description: List of addresses + + # Item endpoints + /items: + get: + tags: [Items] + summary: Get all items + description: Returns all items (committee members only) + responses: + '200': + description: List of items + content: + application/json: + schema: + $ref: '#/components/schemas/ItemListResponse' + + /{address}/items: + get: + tags: [Items] + summary: Get items by address + description: Returns items for a specific address + parameters: + - name: address + in: path + required: true + schema: + type: string + - name: collectionId + in: query + schema: + type: string + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/Limit' + responses: + '200': + description: List of items + content: + application/json: + schema: + $ref: '#/components/schemas/ItemListResponse' + + /items/{id}: + get: + tags: [Items] + summary: Get an item + description: Returns an item by ID + parameters: + - $ref: '#/components/parameters/ItemId' + responses: + '200': + description: Item details + content: + application/json: + schema: + $ref: '#/components/schemas/ItemResponse' + delete: + tags: [Items] + summary: Delete an item + description: Deletes an item + parameters: + - $ref: '#/components/parameters/ItemId' + responses: + '200': + description: Item deleted successfully + + /items/{idOrURN}: + put: + tags: [Items] + summary: Create or update an item + description: Creates a new item or updates an existing one (by ID or URN) + parameters: + - name: idOrURN + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ItemInput' + responses: + '200': + description: Item created/updated successfully + '409': + $ref: '#/components/responses/Conflict' + + /collections/{id}/items: + get: + tags: [Items] + summary: Get collection items + description: Returns items for a specific collection + parameters: + - $ref: '#/components/parameters/CollectionId' + - name: status + in: query + schema: + $ref: '#/components/schemas/CurationStatus' + - name: synced + in: query + schema: + type: string + - name: mappingStatus + in: query + schema: + type: string + enum: [missing_mapping, unpublished_mapping] + - name: name + in: query + schema: + type: string + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/Limit' + responses: + '200': + description: List of items + content: + application/json: + schema: + $ref: '#/components/schemas/ItemListResponse' + + /items/{id}/files: + post: + tags: [Items] + summary: Upload item files + description: Uploads content files for an item + parameters: + - $ref: '#/components/parameters/ItemId' + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + responses: + '200': + description: Files uploaded successfully + + /items/{id}/videos: + post: + tags: [Items] + summary: Upload item video + description: Uploads a video file for an item (max 250MB) + parameters: + - $ref: '#/components/parameters/ItemId' + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + responses: + '200': + description: Video uploaded successfully + + /items/{collectionAddress}/{itemId}/contents: + get: + tags: [Items] + summary: Get item contents + description: Returns item contents by contract address and item ID + security: [] + parameters: + - name: collectionAddress + in: path + required: true + schema: + type: string + - name: itemId + in: path + required: true + schema: + type: string + responses: + '200': + description: Item contents + content: + application/json: + schema: + type: object + additionalProperties: + type: string + + /published-collections/{address}/items/{id}/utility: + get: + tags: [Items] + summary: Get item utility + description: Returns the utility field for a published item + security: [] + parameters: + - name: address + in: path + required: true + schema: + type: string + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Item utility + content: + application/json: + schema: + type: object + properties: + utility: + type: string + nullable: true + + # Curation endpoints + /curations: + get: + tags: [Curations] + summary: Get all collection curations + description: Returns all collection curations + responses: + '200': + description: List of curations + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/CollectionCuration' + + /collections/{id}/curation: + get: + tags: [Curations] + summary: Get collection curation + description: Returns the curation for a collection + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Collection curation + content: + application/json: + schema: + $ref: '#/components/schemas/CollectionCurationResponse' + post: + tags: [Curations] + summary: Create collection curation + description: Creates a new curation for a collection + parameters: + - $ref: '#/components/parameters/CollectionId' + requestBody: + content: + application/json: + schema: + type: object + properties: + assignee: + type: string + responses: + '200': + description: Curation created successfully + patch: + tags: [Curations] + summary: Update collection curation + description: Updates a collection curation + parameters: + - $ref: '#/components/parameters/CollectionId' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/CurationStatus' + assignee: + type: string + responses: + '200': + description: Curation updated successfully + + /collections/{id}/curation/post: + post: + tags: [Curations] + summary: Create curation forum post + description: Creates a forum post for curation assignee change + parameters: + - $ref: '#/components/parameters/CollectionId' + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: Forum post created successfully + + /collections/{id}/itemCurations: + get: + tags: [Curations] + summary: Get collection item curations + description: Returns item curations for a collection + parameters: + - $ref: '#/components/parameters/CollectionId' + - name: itemIds + in: query + schema: + type: array + items: + type: string + responses: + '200': + description: List of item curations + + /items/{id}/curation: + get: + tags: [Curations] + summary: Get item curation + description: Returns the curation for an item + parameters: + - $ref: '#/components/parameters/ItemId' + responses: + '200': + description: Item curation + post: + tags: [Curations] + summary: Create item curation + description: Creates a new curation for an item + parameters: + - $ref: '#/components/parameters/ItemId' + responses: + '200': + description: Curation created successfully + patch: + tags: [Curations] + summary: Update item curation + description: Updates an item curation + parameters: + - $ref: '#/components/parameters/ItemId' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/CurationStatus' + responses: + '200': + description: Curation updated successfully + + # Committee endpoints + /committee/{address}: + get: + tags: [Committee] + summary: Check committee membership + description: Checks if an address is a committee member + security: [] + parameters: + - name: address + in: path + required: true + schema: + type: string + responses: + '200': + description: Committee membership status + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: boolean + + # Third Party endpoints + /thirdParties: + get: + tags: [Third Party] + summary: Get third parties + description: Returns all third-party providers + parameters: + - name: manager + in: query + schema: + type: string + responses: + '200': + description: List of third parties + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/ThirdParty' + + /thirdParties/{id}: + get: + tags: [Third Party] + summary: Get a third party + description: Returns a third party by ID + security: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Third party details + '404': + $ref: '#/components/responses/NotFound' + delete: + tags: [Third Party] + summary: Remove virtual third party + description: Removes a virtual third party after it's on the graph + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Third party removed successfully + patch: + tags: [Third Party] + summary: Update third party + description: Updates a virtual third party + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: Third party updated successfully + + /thirdParties/{id}/slots: + get: + tags: [Third Party] + summary: Get available slots + description: Returns the number of available slots for a third party + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Available slots count + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: integer + + # Rarity endpoints + /rarities: + get: + tags: [Rarities] + summary: Get all rarities + description: Returns all available item rarities with prices + security: [] + responses: + '200': + description: List of rarities + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/Rarity' + + /rarities/{name}: + get: + tags: [Rarities] + summary: Get a rarity + description: Returns a specific rarity by name + security: [] + parameters: + - name: name + in: path + required: true + schema: + type: string + responses: + '200': + description: Rarity details + content: + application/json: + schema: + $ref: '#/components/schemas/RarityResponse' + '404': + $ref: '#/components/responses/NotFound' + + # Forum endpoints + /collections/{id}/post: + get: + tags: [Forum] + summary: Get collection forum post + description: Returns the forum post for a collection + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Forum post details + put: + tags: [Forum] + summary: Update collection forum post + description: Updates or creates the forum post for a collection + parameters: + - $ref: '#/components/parameters/CollectionId' + responses: + '200': + description: Forum post updated successfully + + # Asset Pack endpoints + /assetPacks: + get: + tags: [Asset Packs] + summary: Get all asset packs + description: Returns asset packs for the user or default packs + security: [] + parameters: + - name: owner + in: query + schema: + type: string + description: 'Use "default" for default packs or user address' + responses: + '200': + description: List of asset packs + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/AssetPack' + + /assetPacks/{id}: + get: + tags: [Asset Packs] + summary: Get an asset pack + description: Returns an asset pack by ID + security: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Asset pack details + content: + application/json: + schema: + $ref: '#/components/schemas/AssetPackResponse' + '404': + $ref: '#/components/responses/NotFound' + put: + tags: [Asset Packs] + summary: Create or update an asset pack + description: Creates a new asset pack or updates an existing one + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AssetPackInput' + responses: + '200': + description: Asset pack created/updated successfully + delete: + tags: [Asset Packs] + summary: Delete an asset pack + description: Deletes an asset pack + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Asset pack deleted successfully + + /assetPacks/{id}/thumbnail: + post: + tags: [Asset Packs] + summary: Upload asset pack thumbnail + description: Uploads a thumbnail for an asset pack + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + thumbnail: + type: string + format: binary + responses: + '200': + description: Thumbnail uploaded successfully + + # Asset endpoints + /assetPacks/{id}/assets: + get: + tags: [Assets] + summary: Get assets for an asset pack + description: Returns all assets in an asset pack + security: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: List of assets + + # Manifest endpoints + /projects/{id}/manifest: + get: + tags: [Manifest] + summary: Get project manifest + description: Returns the manifest for a project + security: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '301': + description: Redirect to S3 manifest URL + + /pools/{id}/manifest: + get: + tags: [Manifest] + summary: Get pool manifest + description: Returns the manifest for a pool + security: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '301': + description: Redirect to S3 manifest URL + + # Deployment endpoints + /projects/{id}/deployment: + get: + tags: [Deployments] + summary: Get project deployment + description: Returns deployment info for a project + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: Deployment details + put: + tags: [Deployments] + summary: Create or update deployment + description: Creates or updates deployment info for a project + parameters: + - $ref: '#/components/parameters/ProjectId' + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: Deployment updated successfully + + # Storage endpoints + /storage/assetPacks/{filename}: + get: + tags: [Storage] + summary: Get asset pack file + description: Redirects to S3 URL for asset pack file + security: [] + parameters: + - name: filename + in: path + required: true + schema: + type: string + responses: + '301': + description: Redirect to S3 URL + + /storage/contents/{hash}: + get: + tags: [Storage] + summary: Get content by hash + description: Redirects to S3 URL for content file + security: [] + parameters: + - name: hash + in: path + required: true + schema: + type: string + responses: + '301': + description: Redirect to S3 URL + + # Share endpoints + /share/{type}/{id}: + get: + tags: [Share] + summary: Get share page + description: Returns an HTML share page for a project or pool + security: [] + parameters: + - name: type + in: path + required: true + schema: + type: string + enum: [project, pool] + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Share HTML page + content: + text/html: + schema: + type: string + + # Analytics endpoints + /analytics: + post: + tags: [Analytics] + summary: Track analytics event + description: Sends an analytics event + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: Event tracked successfully + + # NFT endpoints + /nfts/collections: + get: + tags: [NFT] + summary: Get NFT collections + description: Returns NFT collections for an address + parameters: + - name: owner + in: query + required: true + schema: + type: string + responses: + '200': + description: List of NFT collections + + # LAND endpoints + /lands/redirectionHashes: + get: + tags: [LAND] + summary: Get redirection hashes + description: Returns IPFS hashes for LAND redirection files + security: [] + parameters: + - name: coords + in: query + required: true + schema: + oneOf: + - type: string + - type: array + items: + type: string + responses: + '200': + description: Redirection hashes + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + type: object + properties: + x: + type: integer + y: + type: integer + ipfsHash: + type: string + contentHash: + type: string + + /lands/{coords}/redirection: + post: + tags: [LAND] + summary: Upload LAND redirection + description: Uploads a redirection file to IPFS + security: [] + parameters: + - name: coords + in: path + required: true + schema: + type: string + pattern: '^-?[0-9]+,-?[0-9]+$' + responses: + '200': + description: Redirection uploaded + content: + application/json: + schema: + type: object + properties: + ok: + type: boolean + data: + type: object + properties: + ipfsHash: + type: string + contentHash: + type: string + + # Newsletter endpoints + /newsletter: + post: + tags: [Newsletter] + summary: Subscribe to newsletter + description: Subscribes an email to the newsletter + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + properties: + email: + type: string + format: email + responses: + '200': + description: Subscribed successfully + +components: + securitySchemes: + SignedFetch: + type: apiKey + in: header + name: X-Identity-Auth-Chain-* + description: | + Signed fetch authentication using Decentraland's ADR-44 specification. + Multiple headers are used to provide the authentication chain. + + parameters: + ProjectId: + name: id + in: path + required: true + schema: + type: string + format: uuid + description: Project UUID + + CollectionId: + name: id + in: path + required: true + schema: + type: string + format: uuid + description: Collection UUID + + ItemId: + name: id + in: path + required: true + schema: + type: string + format: uuid + description: Item UUID + + Page: + name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + description: Page number for pagination + + Limit: + name: limit + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + description: Number of items per page + + Offset: + name: offset + in: query + schema: + type: integer + minimum: 0 + description: Offset for pagination + + SortBy: + name: sort_by + in: query + schema: + type: string + description: Field to sort by + + SortOrder: + name: sort_order + in: query + schema: + type: string + enum: [asc, desc] + description: Sort order + + responses: + BadRequest: + description: Bad request - invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + Unauthorized: + description: Unauthorized - authentication required or insufficient permissions + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + Conflict: + description: Conflict - resource already exists or is in an invalid state + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + schemas: + ErrorResponse: + type: object + properties: + ok: + type: boolean + example: false + error: + type: string + data: + type: object + + Project: + type: object + properties: + id: + type: string + format: uuid + title: + type: string + description: + type: string + thumbnail: + type: string + scene_id: + type: string + format: uuid + eth_address: + type: string + cols: + type: integer + rows: + type: integer + parcels: + type: integer + transforms: + type: integer + gltf_shapes: + type: integer + nft_shapes: + type: integer + scripts: + type: integer + entities: + type: integer + is_deleted: + type: boolean + is_public: + type: boolean + is_template: + type: boolean + creation_coords: + type: string + template_status: + type: string + enum: [active, coming_soon] + video: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + ProjectInput: + type: object + required: + - id + - title + - description + - scene_id + - cols + - rows + properties: + id: + type: string + format: uuid + title: + type: string + description: + type: string + thumbnail: + type: string + scene_id: + type: string + format: uuid + cols: + type: integer + rows: + type: integer + is_public: + type: boolean + creation_coords: + type: string + is_template: + type: boolean + template_status: + type: string + enum: [active, coming_soon] + video: + type: string + + ProjectResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/Project' + + ProjectListResponse: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/Project' + + Pool: + type: object + allOf: + - $ref: '#/components/schemas/Project' + - type: object + properties: + groups: + type: array + items: + type: string + format: uuid + likes: + type: integer + like: + type: boolean + + PoolResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/Pool' + + PoolListResponse: + type: object + properties: + ok: + type: boolean + data: + type: array + items: + $ref: '#/components/schemas/Pool' + + PoolGroup: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + is_active: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + Collection: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + eth_address: + type: string + salt: + type: string + contract_address: + type: string + urn: + type: string + is_published: + type: boolean + is_approved: + type: boolean + minters: + type: array + items: + type: string + managers: + type: array + items: + type: string + forum_link: + type: string + forum_id: + type: integer + lock: + type: string + format: date-time + reviewed_at: + type: string + format: date-time + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + CollectionInput: + type: object + required: + - id + - name + properties: + id: + type: string + format: uuid + name: + type: string + urn: + type: string + + CollectionResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/Collection' + + CollectionListResponse: + type: object + properties: + ok: + type: boolean + data: + oneOf: + - type: array + items: + $ref: '#/components/schemas/Collection' + - $ref: '#/components/schemas/PaginatedResponse' + + Item: + type: object + properties: + id: + type: string + format: uuid + urn: + type: string + name: + type: string + description: + type: string + thumbnail: + type: string + video: + type: string + eth_address: + type: string + collection_id: + type: string + format: uuid + blockchain_item_id: + type: string + price: + type: string + beneficiary: + type: string + rarity: + type: string + enum: [common, uncommon, rare, epic, legendary, mythic, unique] + type: + type: string + enum: [wearable, emote] + data: + type: object + metrics: + type: object + utility: + type: string + contents: + type: object + additionalProperties: + type: string + mappings: + type: object + is_published: + type: boolean + is_approved: + type: boolean + in_catalyst: + type: boolean + total_supply: + type: integer + content_hash: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + ItemInput: + type: object + required: + - id + - name + - description + - rarity + - type + - data + - contents + properties: + id: + type: string + format: uuid + urn: + type: string + name: + type: string + description: + type: string + collection_id: + type: string + format: uuid + rarity: + type: string + type: + type: string + enum: [wearable, emote] + data: + type: object + contents: + type: object + utility: + type: string + mappings: + type: object + + ItemResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/Item' + + ItemListResponse: + type: object + properties: + ok: + type: boolean + data: + oneOf: + - type: array + items: + $ref: '#/components/schemas/Item' + - $ref: '#/components/schemas/PaginatedResponse' + + ItemApprovalData: + type: object + properties: + cheque: + $ref: '#/components/schemas/Cheque' + content_hashes: + type: object + additionalProperties: + type: string + chequeWasConsumed: + type: boolean + root: + type: string + + Cheque: + type: object + properties: + qty: + type: integer + salt: + type: string + signature: + type: string + + CurationStatus: + type: string + enum: [pending, approved, rejected, to_review, under_review] + + CollectionCuration: + type: object + properties: + id: + type: string + format: uuid + collection_id: + type: string + format: uuid + status: + $ref: '#/components/schemas/CurationStatus' + assignee: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + CollectionCurationResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/CollectionCuration' + + ItemCuration: + type: object + properties: + id: + type: string + format: uuid + item_id: + type: string + format: uuid + status: + $ref: '#/components/schemas/CurationStatus' + content_hash: + type: string + is_mapping_complete: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + ThirdParty: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + managers: + type: array + items: + type: string + maxItems: + type: string + totalItems: + type: string + contracts: + type: array + items: + type: object + + Rarity: + type: object + properties: + id: + type: string + name: + type: string + price: + type: string + maxSupply: + type: string + prices: + type: object + properties: + MANA: + type: string + USD: + type: string + + RarityResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/Rarity' + + AssetPack: + type: object + properties: + id: + type: string + format: uuid + title: + type: string + thumbnail: + type: string + eth_address: + type: string + assets: + type: array + items: + $ref: '#/components/schemas/Asset' + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + AssetPackInput: + type: object + required: + - id + - title + - assets + properties: + id: + type: string + format: uuid + title: + type: string + assets: + type: array + items: + $ref: '#/components/schemas/AssetInput' + + AssetPackResponse: + type: object + properties: + ok: + type: boolean + data: + $ref: '#/components/schemas/AssetPack' + + Asset: + type: object + properties: + id: + type: string + format: uuid + asset_pack_id: + type: string + format: uuid + name: + type: string + model: + type: string + script: + type: string + thumbnail: + type: string + tags: + type: array + items: + type: string + category: + type: string + contents: + type: object + metrics: + type: object + parameters: + type: array + actions: + type: array + + AssetInput: + type: object + required: + - id + - name + - model + properties: + id: + type: string + format: uuid + asset_pack_id: + type: string + format: uuid + name: + type: string + model: + type: string + script: + type: string + thumbnail: + type: string + tags: + type: array + items: + type: string + category: + type: string + contents: + type: object + metrics: + type: object + + PaginatedResponse: + type: object + properties: + results: + type: array + items: + type: object + total: + type: integer + page: + type: integer + pages: + type: integer + limit: + type: integer From ecaa6ea054967afad363764585845903973f3f56 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 11:24:06 -0300 Subject: [PATCH 02/11] chore: Add DB schemas --- docs/database-schemas.md | 621 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 docs/database-schemas.md diff --git a/docs/database-schemas.md b/docs/database-schemas.md new file mode 100644 index 00000000..0611b9cb --- /dev/null +++ b/docs/database-schemas.md @@ -0,0 +1,621 @@ +# Database Schema Documentation + +This document describes the database schema for the Builder Server. The schema uses PostgreSQL and is managed through migrations located in `migrations/`. + +## Database Schema Diagram + +```mermaid +erDiagram + projects ||--o| pools : "becomes" + projects ||--o| deployments : "has" + pools ||--o{ pool_likes : "has" + pools }o--o{ pool_groups : "belongs_to" + + collections ||--o{ items : "contains" + collections ||--o{ collection_curations : "has" + collections ||--o| slot_usage_cheques : "uses" + + items ||--o{ item_curations : "has" + + asset_packs ||--o{ assets : "contains" + + virtual_third_parties ||--o{ collections : "provides" + + projects { + UUID id PK + VARCHAR title + TEXT description + VARCHAR thumbnail + UUID scene_id + VARCHAR eth_address + INTEGER cols + INTEGER rows + INTEGER parcels + INTEGER transforms + INTEGER gltf_shapes + INTEGER nft_shapes + INTEGER scripts + INTEGER entities + BOOLEAN is_deleted + BOOLEAN is_public + BOOLEAN is_template + VARCHAR creation_coords + VARCHAR template_status + VARCHAR video + TIMESTAMP created_at + TIMESTAMP updated_at + } + + pools { + UUID id PK + VARCHAR title + TEXT description + VARCHAR thumbnail + UUID scene_id + VARCHAR eth_address + INTEGER cols + INTEGER rows + INTEGER parcels + INTEGER transforms + INTEGER gltf_shapes + INTEGER nft_shapes + INTEGER scripts + INTEGER entities + UUID_ARRAY groups + INTEGER likes + BOOLEAN is_template + VARCHAR video + VARCHAR template_status + TIMESTAMP created_at + TIMESTAMP updated_at + } + + pool_groups { + UUID id PK + VARCHAR name + BOOLEAN is_active + TIMESTAMP active_from + TIMESTAMP active_until + TIMESTAMP created_at + } + + pool_likes { + UUID pool_id PK + VARCHAR eth_address PK + TIMESTAMP created_at + } + + collections { + UUID id PK + VARCHAR name + VARCHAR eth_address + VARCHAR salt + VARCHAR contract_address + VARCHAR urn_suffix + VARCHAR third_party_id + BOOLEAN is_published + BOOLEAN is_approved + VARCHAR_ARRAY minters + VARCHAR_ARRAY managers + VARCHAR forum_link + INTEGER forum_id + TIMESTAMP lock + TIMESTAMP reviewed_at + VARCHAR linked_contract_address + VARCHAR linked_contract_network + TIMESTAMP created_at + TIMESTAMP updated_at + } + + items { + UUID id PK + VARCHAR urn_suffix + VARCHAR name + TEXT description + VARCHAR thumbnail + VARCHAR video + VARCHAR eth_address + UUID collection_id FK + VARCHAR blockchain_item_id + VARCHAR local_content_hash + VARCHAR price + VARCHAR beneficiary + VARCHAR rarity + VARCHAR type + JSONB data + JSONB metrics + VARCHAR utility + JSONB contents + JSONB mappings + TIMESTAMP created_at + TIMESTAMP updated_at + } + + collection_curations { + UUID id PK + UUID collection_id FK + VARCHAR assignee + VARCHAR status + TIMESTAMP created_at + TIMESTAMP updated_at + } + + item_curations { + UUID id PK + UUID item_id FK + VARCHAR content_hash + VARCHAR status + BOOLEAN is_mapping_complete + TIMESTAMP created_at + TIMESTAMP updated_at + } + + asset_packs { + UUID id PK + VARCHAR title + VARCHAR thumbnail + VARCHAR eth_address + BOOLEAN is_deleted + TIMESTAMP created_at + TIMESTAMP updated_at + } + + assets { + UUID id PK + UUID asset_pack_id FK + VARCHAR legacy_id + VARCHAR name + VARCHAR model + VARCHAR script + VARCHAR thumbnail + VARCHAR_ARRAY tags + VARCHAR category + JSONB metrics + JSONB contents + JSONB parameters + JSONB actions + } + + deployments { + UUID id PK + VARCHAR eth_address + VARCHAR last_published_cid + BOOLEAN is_dirty + INTEGER x + INTEGER y + VARCHAR rotation + } + + virtual_third_parties { + VARCHAR id PK + VARCHAR_ARRAY managers + TEXT raw_metadata + BOOLEAN isProgrammatic + TIMESTAMP created_at + TIMESTAMP updated_at + } + + slot_usage_cheques { + UUID id PK + INTEGER qty + VARCHAR salt + VARCHAR signature + UUID collection_id FK + VARCHAR third_party_id + TIMESTAMP created_at + TIMESTAMP updated_at + } +``` + +## Tables Overview + +The database contains the following main tables: + +1. **`projects`** - Scene projects created by users +2. **`pools`** - Public scene pools for community sharing +3. **`pool_groups`** - Categories for organizing pools +4. **`pool_likes`** - User likes on pools +5. **`collections`** - Wearable and emote collections +6. **`items`** - Individual wearables and emotes within collections +7. **`collection_curations`** - Curation status for collections +8. **`item_curations`** - Curation status for individual items +9. **`asset_packs`** - Asset pack definitions +10. **`assets`** - Individual 3D assets within packs +11. **`deployments`** - Scene deployment tracking +12. **`virtual_third_parties`** - Third-party provider definitions +13. **`slot_usage_cheques`** - Third-party slot usage records + +--- + +## Table: `projects` + +Stores scene projects created by users in the Builder. + +### Columns + +| Column | Type | Nullable | Description | +| ----------------- | --------- | -------- | -------------------------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Unique identifier for the project | +| `title` | VARCHAR | NOT NULL | Project title | +| `description` | TEXT | NULL | Project description | +| `thumbnail` | VARCHAR | NULL | Thumbnail filename | +| `scene_id` | UUID | NOT NULL | Scene identifier | +| `eth_address` | VARCHAR | NULL | Owner's Ethereum address (lowercase) | +| `cols` | INTEGER | NOT NULL | Number of columns (parcels X) | +| `rows` | INTEGER | NOT NULL | Number of rows (parcels Y) | +| `parcels` | INTEGER | NULL | Total parcel count | +| `transforms` | INTEGER | NULL | Number of transforms | +| `gltf_shapes` | INTEGER | NULL | Number of GLTF shapes | +| `nft_shapes` | INTEGER | NULL | Number of NFT shapes | +| `scripts` | INTEGER | NULL | Number of scripts | +| `entities` | INTEGER | NULL | Number of entities | +| `is_deleted` | BOOLEAN | NOT NULL | Soft delete flag (default: false) | +| `is_public` | BOOLEAN | NOT NULL | Public visibility flag | +| `is_template` | BOOLEAN | NULL | Template flag | +| `creation_coords` | VARCHAR | NULL | Creation coordinates | +| `template_status` | VARCHAR | NULL | Template status: 'active', 'coming_soon' | +| `video` | VARCHAR | NULL | Video URL | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Index on `eth_address` +- Index on `is_deleted` +- Index on `is_template` + +### Business Rules + +- **Soft delete**: Projects are never hard deleted; `is_deleted` is set to true +- **Address normalization**: `eth_address` is stored in lowercase + +--- + +## Table: `pools` + +Stores public scene pools for community sharing. Pools are derived from projects. + +### Columns + +| Column | Type | Nullable | Description | +| ----------------- | --------- | -------- | ----------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Same as project ID | +| `title` | VARCHAR | NOT NULL | Pool title | +| `description` | TEXT | NULL | Pool description | +| `thumbnail` | VARCHAR | NULL | Thumbnail filename | +| `scene_id` | UUID | NOT NULL | Scene identifier | +| `eth_address` | VARCHAR | NULL | Owner's Ethereum address | +| `cols` | INTEGER | NOT NULL | Number of columns | +| `rows` | INTEGER | NOT NULL | Number of rows | +| `parcels` | INTEGER | NULL | Total parcel count | +| `transforms` | INTEGER | NULL | Number of transforms | +| `gltf_shapes` | INTEGER | NULL | Number of GLTF shapes | +| `nft_shapes` | INTEGER | NULL | Number of NFT shapes | +| `scripts` | INTEGER | NULL | Number of scripts | +| `entities` | INTEGER | NULL | Number of entities | +| `groups` | UUID[] | NULL | Array of pool group IDs | +| `likes` | INTEGER | NOT NULL | Like count (default: 0) | +| `is_template` | BOOLEAN | NULL | Template flag | +| `video` | VARCHAR | NULL | Video URL | +| `template_status` | VARCHAR | NULL | Template status | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` + +--- + +## Table: `pool_groups` + +Stores categories for organizing pools. + +### Columns + +| Column | Type | Nullable | Description | +| -------------- | --------- | -------- | --------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Group identifier | +| `name` | VARCHAR | NOT NULL | Group name | +| `is_active` | BOOLEAN | NULL | Active status | +| `active_from` | TIMESTAMP | NOT NULL | Active start date | +| `active_until` | TIMESTAMP | NOT NULL | Active end date | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | + +### Business Rules + +- **Active window**: Groups are active between `active_from` and `active_until` + +--- + +## Table: `pool_likes` + +Stores user likes on pools. + +### Columns + +| Column | Type | Nullable | Description | +| ------------- | --------- | -------- | ----------------------------------------- | +| `pool_id` | UUID | NOT NULL | **Composite Primary Key**. Pool reference | +| `eth_address` | VARCHAR | NOT NULL | **Composite Primary Key**. User address | +| `created_at` | TIMESTAMP | NOT NULL | Like timestamp | + +### Constraints + +- **Composite Primary Key**: (`pool_id`, `eth_address`) +- **One like per user per pool**: Enforced by primary key + +--- + +## Table: `collections` + +Stores wearable and emote collections. + +### Columns + +| Column | Type | Nullable | Description | +| ------------------------- | --------- | -------- | ------------------------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Collection identifier | +| `name` | VARCHAR | NOT NULL | Collection name | +| `eth_address` | VARCHAR | NOT NULL | Owner's Ethereum address | +| `salt` | VARCHAR | NULL | Random salt for contract address generation | +| `contract_address` | VARCHAR | NULL | Deployed contract address (lowercase) | +| `urn_suffix` | VARCHAR | NULL | URN suffix for third-party collections | +| `third_party_id` | VARCHAR | NULL | Third-party provider ID | +| `is_published` | BOOLEAN | NOT NULL | Publication status | +| `is_approved` | BOOLEAN | NOT NULL | Approval status | +| `minters` | VARCHAR[] | NOT NULL | Array of minter addresses | +| `managers` | VARCHAR[] | NOT NULL | Array of manager addresses | +| `forum_link` | VARCHAR | NULL | Forum post URL | +| `forum_id` | INTEGER | NULL | Forum post ID | +| `lock` | TIMESTAMP | NULL | Lock timestamp (prevents edits until publication) | +| `reviewed_at` | TIMESTAMP | NULL | Last review timestamp | +| `linked_contract_address` | VARCHAR | NULL | Linked contract for third-party | +| `linked_contract_network` | VARCHAR | NULL | Network of linked contract | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Unique index on (`third_party_id`, `urn_suffix`) +- Index on `eth_address` +- Index on `contract_address` + +### Business Rules + +- **Standard vs Third-Party**: Standard collections have `third_party_id = NULL`; third-party collections have both `third_party_id` and `urn_suffix` set +- **Lock mechanism**: When `lock` is set, the collection cannot be modified until published +- **Address normalization**: All addresses are stored in lowercase + +--- + +## Table: `items` + +Stores individual wearables and emotes within collections. + +### Columns + +| Column | Type | Nullable | Description | +| -------------------- | --------- | -------- | --------------------------------------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Item identifier | +| `urn_suffix` | VARCHAR | NULL | URN suffix for third-party items | +| `name` | VARCHAR | NOT NULL | Item name | +| `description` | TEXT | NOT NULL | Item description | +| `thumbnail` | VARCHAR | NOT NULL | Thumbnail hash | +| `video` | VARCHAR | NULL | Video hash for smart wearables | +| `eth_address` | VARCHAR | NOT NULL | Creator's Ethereum address | +| `collection_id` | UUID | NULL | **Foreign Key** to collections | +| `blockchain_item_id` | VARCHAR | NULL | Blockchain item ID after publication | +| `local_content_hash` | VARCHAR | NULL | Local content hash for sync detection | +| `price` | VARCHAR | NULL | Item price in wei | +| `beneficiary` | VARCHAR | NULL | Beneficiary address for sales | +| `rarity` | VARCHAR | NOT NULL | Rarity: common, uncommon, rare, epic, legendary, mythic, unique | +| `type` | VARCHAR | NOT NULL | Type: wearable, emote | +| `data` | JSONB | NOT NULL | Type-specific data (wearable/emote metadata) | +| `metrics` | JSONB | NOT NULL | Model metrics (triangles, materials, etc.) | +| `utility` | VARCHAR | NULL | Utility description | +| `contents` | JSONB | NOT NULL | Content file hashes | +| `mappings` | JSONB | NULL | Third-party mappings | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Index on `collection_id` +- Index on `eth_address` +- Unique index on (`urn_suffix`) within same third-party + +### Business Rules + +- **Orphan items**: Items without `collection_id` are orphan items not yet assigned to a collection +- **Publication**: Items are published when `blockchain_item_id` is set +- **Content sync**: `local_content_hash` is compared with curation `content_hash` to detect changes + +--- + +## Table: `collection_curations` + +Stores curation status for collections. + +### Columns + +| Column | Type | Nullable | Description | +| --------------- | --------- | -------- | ------------------------------------ | +| `id` | UUID | NOT NULL | **Primary Key**. Curation identifier | +| `collection_id` | UUID | NOT NULL | **Foreign Key** to collections | +| `assignee` | VARCHAR | NULL | Committee member assigned | +| `status` | VARCHAR | NOT NULL | Status: pending, approved, rejected | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Index on `collection_id` + +### Business Rules + +- **Latest curation**: The most recent curation (by `created_at`) is the active one +- **Assignee**: Only committee members can be assigned + +--- + +## Table: `item_curations` + +Stores curation status for individual items (third-party items). + +### Columns + +| Column | Type | Nullable | Description | +| --------------------- | --------- | -------- | ------------------------------------ | +| `id` | UUID | NOT NULL | **Primary Key**. Curation identifier | +| `item_id` | UUID | NOT NULL | **Foreign Key** to items | +| `content_hash` | VARCHAR | NOT NULL | Content hash at time of curation | +| `status` | VARCHAR | NOT NULL | Status: pending, approved, rejected | +| `is_mapping_complete` | BOOLEAN | NULL | Whether mappings are complete | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Index on `item_id` + +--- + +## Table: `asset_packs` + +Stores asset pack definitions. + +### Columns + +| Column | Type | Nullable | Description | +| ------------- | --------- | -------- | -------------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Asset pack identifier | +| `title` | VARCHAR | NOT NULL | Pack title (3-20 characters) | +| `thumbnail` | VARCHAR | NULL | Thumbnail filename | +| `eth_address` | VARCHAR | NULL | Owner's Ethereum address | +| `is_deleted` | BOOLEAN | NOT NULL | Soft delete flag | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Index on `eth_address` + +### Business Rules + +- **Default packs**: Packs with the default eth address are available to all users +- **Soft delete**: Asset packs are soft deleted +- **Asset limit**: Max 80 assets per pack (for packs created after limit split date) + +--- + +## Table: `assets` + +Stores individual 3D assets within packs. + +### Columns + +| Column | Type | Nullable | Description | +| --------------- | --------- | -------- | --------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Asset identifier | +| `asset_pack_id` | UUID | NOT NULL | **Foreign Key** to asset_packs | +| `legacy_id` | VARCHAR | NULL | Legacy identifier for migration | +| `name` | VARCHAR | NOT NULL | Asset name (3-50 characters) | +| `model` | VARCHAR | NOT NULL | Model filename | +| `script` | VARCHAR | NULL | Script filename | +| `thumbnail` | VARCHAR | NULL | Thumbnail filename | +| `tags` | VARCHAR[] | NOT NULL | Search tags | +| `category` | VARCHAR | NOT NULL | Asset category | +| `metrics` | JSONB | NOT NULL | Model metrics | +| `contents` | JSONB | NOT NULL | Content file mappings | +| `parameters` | JSONB | NULL | Configurable parameters | +| `actions` | JSONB | NULL | Available actions | + +### Indexes + +- **Primary Key**: `id` +- Index on `asset_pack_id` + +--- + +## Table: `deployments` + +Stores scene deployment tracking. + +### Columns + +| Column | Type | Nullable | Description | +| -------------------- | ------- | -------- | --------------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Same as project ID | +| `eth_address` | VARCHAR | NULL | Deployer's address | +| `last_published_cid` | VARCHAR | NULL | Last published content ID | +| `is_dirty` | BOOLEAN | NOT NULL | Whether project has unpublished changes | +| `x` | INTEGER | NOT NULL | Parcel X coordinate | +| `y` | INTEGER | NOT NULL | Parcel Y coordinate | +| `rotation` | VARCHAR | NOT NULL | Rotation: north, east, south, west | + +### Indexes + +- **Primary Key**: `id` + +--- + +## Table: `virtual_third_parties` + +Stores third-party provider definitions created through the Builder. + +### Columns + +| Column | Type | Nullable | Description | +| ---------------- | --------- | -------- | --------------------------------------- | +| `id` | VARCHAR | NOT NULL | **Primary Key**. Third-party URN | +| `managers` | VARCHAR[] | NOT NULL | Manager addresses | +| `raw_metadata` | TEXT | NOT NULL | Raw metadata JSON | +| `isProgrammatic` | BOOLEAN | NOT NULL | Whether it's a programmatic third party | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` + +### Business Rules + +- **Virtual vs On-chain**: Virtual third parties exist only in the database until added to the blockchain +- **Deletion**: Can only be deleted after being added to the graph + +--- + +## Table: `slot_usage_cheques` + +Stores third-party slot usage cheques for item publication. + +### Columns + +| Column | Type | Nullable | Description | +| ---------------- | --------- | -------- | ---------------------------------- | +| `id` | UUID | NOT NULL | **Primary Key**. Cheque identifier | +| `qty` | INTEGER | NOT NULL | Number of slots used | +| `salt` | VARCHAR | NOT NULL | Random salt for signature | +| `signature` | VARCHAR | NOT NULL | Cryptographic signature | +| `collection_id` | UUID | NOT NULL | **Foreign Key** to collections | +| `third_party_id` | VARCHAR | NOT NULL | Third-party identifier | +| `created_at` | TIMESTAMP | NOT NULL | Creation timestamp | +| `updated_at` | TIMESTAMP | NOT NULL | Last update timestamp | + +### Indexes + +- **Primary Key**: `id` +- Index on `collection_id` +- Index on `third_party_id` + +### Business Rules + +- **One cheque per collection**: Each collection has at most one active cheque +- **Signature verification**: Signature is verified against third-party manager From c8b62b5947dddb44991bbd8c94d85ffab762a521 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 11:24:19 -0300 Subject: [PATCH 03/11] chore: Add AI agent context docs --- docs/ai-agent-context.md | 144 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/ai-agent-context.md diff --git a/docs/ai-agent-context.md b/docs/ai-agent-context.md new file mode 100644 index 00000000..c5d70a7a --- /dev/null +++ b/docs/ai-agent-context.md @@ -0,0 +1,144 @@ +# AI Agent Context + +**Service Purpose:** + +The Builder Server is the backend service for the Decentraland Builder application. It provides APIs for creating and managing scene projects, wearable and emote collections, asset packs, and facilitates the publication workflow to the Decentraland platform. + +**Key Capabilities:** + +- **Scene Project Management**: Create, edit, and deploy scene projects to Decentraland LAND parcels +- **Wearable/Emote Collections**: Manage collections of wearables and emotes with full lifecycle support +- **Item Curation Workflow**: Complete approval workflow with committee review for publishing items +- **Third-Party Collections**: Support for external providers to create and manage collections +- **Asset Pack Management**: Create and share reusable 3D asset packs for scene building +- **Pool System**: Public scene pools for community sharing with likes and categorization +- **S3 Storage Integration**: Manage project files, thumbnails, and content through S3-compatible storage +- **Forum Integration**: Automatic forum post creation for collection submissions + +**Communication Pattern:** + +- HTTP REST API with JSON payloads +- Authentication via Decentraland Signed Fetch (ADR-44) using `X-Identity-Auth-Chain-*` headers +- API versioning through URL prefix (default: `/v1/`) +- CORS-enabled for browser clients + +**Technology Stack:** + +- Runtime: Node.js (check `.nvmrc` for version) +- Language: TypeScript +- HTTP Framework: Express.js +- Database: PostgreSQL with `node-pg-migrate` for migrations +- ORM: `decentraland-server` Model class +- Storage: AWS S3 / MinIO (S3-compatible) +- GraphQL Client: Apollo Client for blockchain data queries + +**External Dependencies:** + +- **PostgreSQL**: Primary database for all service data +- **AWS S3 / MinIO**: Object storage for projects, assets, and content files +- **Decentraland Subgraph APIs**: Blockchain data for collections, items, rarities, and third parties +- **Catalyst Servers**: Content servers for deployed scenes and wearables +- **Discourse Forum API**: Forum integration for collection submission posts + +**Key Concepts:** + +- **URN (Uniform Resource Name)**: Unique identifier for Decentraland assets. Format varies: + + - Standard collections: `urn:decentraland:matic:collections-v2:{contract_address}` + - Third-party items: `urn:decentraland:matic:collections-thirdparty:{third_party_id}:{collection_urn_suffix}:{item_urn_suffix}` + +- **Standard vs Third-Party Collections**: + + - Standard collections are deployed to Decentraland's collection factory contract + - Third-party collections are managed by external providers with their own contracts + +- **Curation Workflow**: + + - Collections/items go through `pending` → `approved`/`rejected` states + - Committee members can assign themselves and review submissions + - Approved items can be published to the blockchain + +- **Content Hashing**: + + - Items have `local_content_hash` computed from their contents + - Curations store `content_hash` at approval time + - Comparison detects if items have been modified since approval + +- **Lock Mechanism**: + + - Collections can be locked (`lock` timestamp) before publication + - Locked collections cannot be modified until published + +- **Pools vs Projects**: + + - Projects are user-owned scene configurations + - Pools are public copies of projects shared with the community + +- **Asset Packs**: + + - Reusable collections of 3D assets for scene building + - Default packs are available to all users + - Users can create custom packs (max 80 assets) + +- **Slot Usage Cheques**: + - Third-party collections require "slots" to publish items + - Cheques are signed authorizations to use slots + +**Database Notes:** + +- **Address Normalization**: All Ethereum addresses are stored in lowercase for consistent querying + +- **Soft Deletes**: Projects and asset packs use `is_deleted` flag instead of hard deletes + +- **UUID Primary Keys**: Most tables use UUID primary keys generated client-side + +- **JSONB Columns**: Complex data like item `data`, `metrics`, `contents`, and `mappings` are stored as JSONB + +- **Curation History**: Both `collection_curations` and `item_curations` tables store history; the latest record (by `created_at`) is the active curation + +- **Composite Keys**: `pool_likes` uses composite primary key (`pool_id`, `eth_address`) to enforce one like per user + +- **Third-Party Items**: Items with `urn_suffix` set belong to third-party collections; standard items have `urn_suffix = NULL` + +**Code Organization:** + +``` +src/ +├── {Entity}/ # Feature modules +│ ├── {Entity}.model.ts # Database model +│ ├── {Entity}.router.ts # Express router +│ ├── {Entity}.service.ts # Business logic (optional) +│ ├── {Entity}.types.ts # TypeScript types +│ └── index.ts # Module exports +├── common/ # Shared utilities +├── database/ # Database connection +├── ethereum/ # Blockchain API clients +├── middleware/ # Express middleware +├── S3/ # S3 storage utilities +└── utils/ # General utilities +``` + +**Testing:** + +- Tests are in `spec/` directory and alongside source files (`*.spec.ts`) +- Uses Jest with `@swc/jest` for fast compilation +- Run with `npm test` or `npm run test:coverage` + +**Common Patterns:** + +- **Router Structure**: Each router extends a base `Router` class and mounts endpoints in `mount()` method +- **Authentication Middleware**: Use `withAuthentication` for authenticated routes, `withPermissiveAuthentication` for optional auth +- **Model Authorization**: Use `withModelAuthorization` to check ownership before operations +- **Error Handling**: Throw `HTTPError` with status codes from `STATUS_CODES` +- **Request Extraction**: Use `server.extractFromReq(req, 'paramName')` to get request parameters + +**Environment Variables:** + +Key configuration via environment variables: + +- `SERVER_PORT`: Server port (default: 5000) +- `API_VERSION`: API version prefix (default: v1) +- `CONNECTION_STRING`: PostgreSQL connection string +- `AWS_*`: S3/MinIO configuration +- `PEER_URL`: Catalyst peer URL +- `BUILDER_SERVER_URL`: Public URL of this server From e3f517df9307c7174b243e841e7c74528395ec92 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 11:24:28 -0300 Subject: [PATCH 04/11] chore: Update README --- README.md | 285 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 230 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 1d3eb0de..eb4672f6 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,178 @@ # Builder Server -Exposes endpoints for the Builder. +Backend service for the Decentraland Builder application. Exposes endpoints for managing scene projects, wearable/emote collections, asset packs, and more. -## RUN +## Table of Contents -Check `.env.example` and create your own `.env` file. Some properties have defaults. +- [Features](#features) +- [Dependencies & Related Services](#dependencies--related-services) +- [API Documentation](#api-documentation) +- [Database](#database) + - [Schema](#schema) + - [Migrations](#migrations) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Configuration](#configuration) + - [Running the Service](#running-the-service) +- [S3 Storage Structure](#s3-storage-structure) +- [Testing](#testing) +- [AI Agent Context](#ai-agent-context) + +## Features + +- **Scene Projects**: Create, manage, and deploy scene projects to Decentraland parcels +- **Collections & Items**: Manage wearable and emote collections with full curation workflow +- **Asset Packs**: Create and manage reusable 3D asset packs for scene building +- **Pools & Pool Groups**: Public scene pools for community sharing and categorization +- **Third Party Integration**: Support for third-party wearable and emote collections +- **S3 Storage**: Manage project files, thumbnails, and content through S3-compatible storage +- **Curation Workflow**: Full approval workflow for collections and items with committee review +- **Forum Integration**: Automatic forum post creation for collection submissions + +## Dependencies & Related Services + +This service interacts with the following services: + +- **[Decentraland Builder](https://github.com/decentraland/builder)**: Frontend application that consumes this API +- **[Catalyst](https://github.com/decentraland/catalyst)**: Content server for deployed scenes and wearables +- **[Subgraph APIs](https://subgraph.decentraland.org)**: Blockchain data for collections, items, and third parties + +External dependencies: + +- **PostgreSQL**: Primary database for all service data +- **AWS S3 / MinIO**: Object storage for projects, assets, and content files +- **Discourse Forum API**: Forum integration for collection submissions +- **Decentraland Graph APIs**: Collection, item, and third-party blockchain data + +## API Documentation + +The API is fully documented using the [OpenAPI standard](https://swagger.io/specification/). The schema is located at [docs/openapi.yaml](docs/openapi.yaml). + +### S3 Rewrites + +The following endpoints proxy requests to S3 storage: + +- `GET /v1/storage/assetPacks/:filename` → Asset pack thumbnails (`:id.png`) +- `GET /v1/storage/contents/:hash` → Content files by hash + +### Authenticated S3 Endpoints + +These endpoints require authentication: + +- `GET /v1/projects/:id/manifest` → Project manifest JSON +- `GET /v1/pools/:id/manifest` → Pool manifest JSON +- `GET /v1/projects/:id/media/:filename` → Project media files + +> **Note**: `/v1/` corresponds to the version specified in `.env` with `API_VERSION` + +## Database + +### Schema + +See [docs/database-schemas.md](docs/database-schemas.md) for detailed schema, column definitions, and relationships. + +### Migrations + +The service uses `node-pg-migrate` for database migrations. These migrations are located in `migrations/`. + +#### Create a new migration + +Migrations are created by running the create command: ```bash +npm run migrate -- create name-of-the-migration +``` -#Create database (Only the first time) -createdb +This will create a migration file inside the `migrations/` directory containing the migration setup and rollback procedures. -# Update env. The DEFAULT_USER_ID is important +#### Manually applying migrations -# Update state -npm i -npm run migrate up +To run migrations manually: -# Only once -npx ts-node ./scripts/parseS3Pools.ts -npx ts-node ./scripts/updateProjectThumbnails.ts +```bash +npm run migrate up +``` -# On each asset pack change -npm run seed +To rollback migrations manually: -# Start -npm start +```bash +npm run migrate down ``` -# Rewrites to S3 +## Getting Started -- `GET /v1/storage/assetPacks/:filename` => https://s3.amazonaws.com/nico.decentraland.zone/asset_packs/:filename (for the thumbnail, :id.png) -- `GET /v1/storage/contents/:hash` => https://s3.amazonaws.com/nico.decentraland.zone/contents/:hash +### Prerequisites -# Also S3 but behind auth +Before running this service, ensure you have the following installed: -- `GET /v1/projects/:id/manifest` => https://s3.amazonaws.com/nico.decentraland.zone/projects/:id/manifest.json -- `GET /v1/pools/:id/manifest` => https://s3.amazonaws.com/nico.decentraland.zone/projects/:id/pool.json -- `GET /v1/projects/:id/media/:filename` => https://s3.amazonaws.com/nico.decentraland.zone/projects/:id/filename +- **Node.js**: Check `.nvmrc` for the required version +- **npm**: Comes with Node.js +- **PostgreSQL**: Version 12.x or higher +- **Docker**: For containerized external services (optional) -Take into account that `/v1/` correspond to the version that you specify in the file `.env` with `API_VERSION` +### Installation -# S3 structure +1. Clone the repository: ```bash -projects -|____PROJECT_ID -| |____manifest.json -| |____pool.json -| |____east.png -| |____north.png -| |____preview.png -| |____south.png -| |____thumbnail.png -| |____west.png -asset_packs -|____ASSET_PACK_ID.png -contents -|____HASH1 -|____HASH2 -|____HASH3 +git clone https://github.com/decentraland/builder-server.git +cd builder-server ``` -#Extra Info -If you are using windows subsystem, you will need to start the postgresql service each time +2. Install dependencies: -`sudo service postgresql start` +```bash +npm install +``` -# Running external services locally with Docker Compose +3. Build the project: -If you have docker running on your machine and want to have external dependencies running locally on your machine, -you can use the `docker-compose` file present in the repo to do it in a simple and centralized way and avoid the hassle of -configuring each one of them independantly. +```bash +npm run build +``` + +### Configuration + +The service uses environment variables for configuration. Create a `.env` file in the root directory based on `.env.example`: + +```bash +cp .env.example .env +``` -First, run all services simultaneously with: +Key environment variables: -`docker-compose up -d` +| Variable | Description | +| ------------------- | -------------------------------------- | +| `SERVER_PORT` | Port to run the server (default: 5000) | +| `API_VERSION` | API version prefix (default: v1) | +| `CONNECTION_STRING` | PostgreSQL connection string | +| `DEFAULT_USER_ID` | Default user ID for seeding | +| `AWS_ACCESS_KEY` | AWS/MinIO access key | +| `AWS_ACCESS_SECRET` | AWS/MinIO secret key | +| `AWS_BUCKET_NAME` | S3 bucket name | +| `AWS_STORAGE_URL` | S3-compatible storage URL | -`-d or --detach` will run the processes on the background instead of running on the terminal which executed the command. -You can ignore this flag if you don't care about it. +### Running the Service -Before running the `builder-server`, make sure that the following `.env` variables are set correctly so the services run by docker-compose work as expected. +#### Using Docker Compose (Recommended for Development) +This repository provides a `docker-compose.yml` file to run external dependencies locally: + +```bash +docker-compose up -d ``` + +This starts: + +- **PostgreSQL** on port 5432 +- **Adminer** (database UI) on http://localhost:8080 +- **MinIO** (S3-compatible storage) on port 9000 +- **MinIO Console** on http://localhost:9001 + +Configure your `.env` for Docker Compose: + +```env CONNECTION_STRING='postgres://admin:password@localhost:5432/builder-server' AWS_ACCESS_KEY=admin AWS_ACCESS_SECRET=password @@ -90,6 +180,91 @@ AWS_BUCKET_NAME=builder-server AWS_STORAGE_URL=http://localhost:9000 ``` -You can then run the `builder-service` normally as instructed in [RUN](#run) +#### First-time Setup + +```bash +# Run database migrations +npm run migrate up + +# Seed initial data (only once, after asset pack changes) +npm run seed + +# Optional: Run one-time scripts +npx ts-node ./scripts/parseS3Pools.ts +npx ts-node ./scripts/updateProjectThumbnails.ts +``` + +#### Running in Development Mode + +```bash +npm start +``` + +Or with auto-reload: + +```bash +npm run watch:start +``` + +#### Windows Subsystem for Linux (WSL) + +If using WSL, start PostgreSQL service before running: + +```bash +sudo service postgresql start +``` + +## S3 Storage Structure + +``` +projects/ +├── PROJECT_ID/ +│ ├── manifest.json +│ ├── pool.json +│ ├── east.png +│ ├── north.png +│ ├── preview.png +│ ├── south.png +│ ├── thumbnail.png +│ └── west.png +asset_packs/ +└── ASSET_PACK_ID.png +contents/ +├── HASH1 +├── HASH2 +└── HASH3 +``` + +## Testing + +This service includes test coverage with unit and integration tests. + +### Running Tests + +Run all tests: + +```bash +npm test +``` + +Run tests in watch mode: + +```bash +npm run test:watch +``` + +Run tests with coverage: + +```bash +npm run test:coverage +``` + +For detailed testing guidelines and standards, refer to the [Decentraland Testing Standards](https://decentraland.notion.site/Testing-standards-46797744fccf4f3eba52335f9866d0eb). + +## AI Agent Context + +For detailed AI Agent context, see [docs/ai-agent-context.md](docs/ai-agent-context.md). + +--- -This method also provides some utilities to facilitate developer experience with a database viewer that can be accessed in http://localhost:8080, as well as an object storage viewer in http://localhost:9001 +**License**: Apache-2.0 From 746e177f6101e3958a18945d9f94adbf92df4293 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 12:34:15 -0300 Subject: [PATCH 05/11] chore: Migrate to node 24 --- .github/workflows/node.yml | 6 +++--- Dockerfile | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 8217abac..542be7e6 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -8,12 +8,12 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v2 - - name: Use Node.js 22.x + - name: Use Node.js 24.x uses: actions/setup-node@v4.0.1 with: - node-version: '22.x' + node-version: '24.x' - name: Install - run: npm ci + run: npm install - name: Test run: npm run test:coverage if: ${{ always() }} diff --git a/Dockerfile b/Dockerfile index ad591542..d2114cfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,11 @@ ARG RUN -FROM node:18.17.1-alpine as builder +FROM node:24.12.0-alpine as builder WORKDIR /app # The catalyst client lib is using a dependency installed by using git -RUN apk update -RUN apk add git +RUN apk add --no-cache git python3 build-base COPY package.json /app/package.json COPY package-lock.json /app/package-lock.json @@ -18,7 +17,7 @@ COPY . /app RUN npm run build -FROM node:18.17.1-alpine +FROM node:24.12.0-alpine WORKDIR /app From 7f6714a207d65feacc9b3948b614c28ee6be7851 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 12:34:32 -0300 Subject: [PATCH 06/11] chore: Add OpenAI pipeline --- .github/workflows/docs.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/docs.yaml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 00000000..ba7ef2e6 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,18 @@ +name: build-app-docs +on: + push: + branches: [main] + paths: + - 'docs/**' + pull_request: + paths: + - 'docs/**' + +jobs: + build: + uses: decentraland/platform-actions/.github/workflows/apps-docs.yml@main + with: + api-spec-file: 'docs/openapi.yaml' + api-spec-name: 'builder-server-api' + node-version: '24' + secrets: inherit From 2430f7cf51d3ddfa314a5ec95ea91a7720c9dbb3 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 14:18:05 -0300 Subject: [PATCH 07/11] fix: Permission during building docs --- .github/workflows/docs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index ba7ef2e6..6cac0273 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,4 +1,5 @@ name: build-app-docs + on: push: branches: [main] @@ -10,6 +11,8 @@ on: jobs: build: + permissions: + contents: read uses: decentraland/platform-actions/.github/workflows/apps-docs.yml@main with: api-spec-file: 'docs/openapi.yaml' From 93e1b76ade7a6b886c25c7b48f0d372227b7dbc5 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 22 Dec 2025 15:31:32 -0300 Subject: [PATCH 08/11] fix: Add link reference to the ADR-44 --- docs/ai-agent-context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ai-agent-context.md b/docs/ai-agent-context.md index c5d70a7a..b86bc6bb 100644 --- a/docs/ai-agent-context.md +++ b/docs/ai-agent-context.md @@ -18,7 +18,7 @@ The Builder Server is the backend service for the Decentraland Builder applicati **Communication Pattern:** - HTTP REST API with JSON payloads -- Authentication via Decentraland Signed Fetch (ADR-44) using `X-Identity-Auth-Chain-*` headers +- Authentication via Decentraland Signed Fetch [ADR-44](https://adr.decentraland.org/adr/ADR-44) using `X-Identity-Auth-Chain-*` headers - API versioning through URL prefix (default: `/v1/`) - CORS-enabled for browser clients From b42ea20839b7cda1d47c350eb4a142c47fdef90d Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Tue, 23 Dec 2025 11:30:57 -0300 Subject: [PATCH 09/11] fix: npm ci --- .github/workflows/node.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 542be7e6..6f83d0b9 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -13,7 +13,7 @@ jobs: with: node-version: '24.x' - name: Install - run: npm install + run: npm ci - name: Test run: npm run test:coverage if: ${{ always() }} From 3ee58a4a98affad0ea20079047b7a92fe0931665 Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 29 Dec 2025 12:18:39 -0300 Subject: [PATCH 10/11] docs: Update readme --- README.md | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/README.md b/README.md index eb4672f6..265b8076 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,6 @@ This service interacts with the following services: - **[Decentraland Builder](https://github.com/decentraland/builder)**: Frontend application that consumes this API - **[Catalyst](https://github.com/decentraland/catalyst)**: Content server for deployed scenes and wearables - **[Subgraph APIs](https://subgraph.decentraland.org)**: Blockchain data for collections, items, and third parties - -External dependencies: - - **PostgreSQL**: Primary database for all service data - **AWS S3 / MinIO**: Object storage for projects, assets, and content files - **Discourse Forum API**: Forum integration for collection submissions @@ -140,18 +137,7 @@ The service uses environment variables for configuration. Create a `.env` file i cp .env.example .env ``` -Key environment variables: - -| Variable | Description | -| ------------------- | -------------------------------------- | -| `SERVER_PORT` | Port to run the server (default: 5000) | -| `API_VERSION` | API version prefix (default: v1) | -| `CONNECTION_STRING` | PostgreSQL connection string | -| `DEFAULT_USER_ID` | Default user ID for seeding | -| `AWS_ACCESS_KEY` | AWS/MinIO access key | -| `AWS_ACCESS_SECRET` | AWS/MinIO secret key | -| `AWS_BUCKET_NAME` | S3 bucket name | -| `AWS_STORAGE_URL` | S3-compatible storage URL | +See `.env.example` for all available configuration options with descriptions. ### Running the Service @@ -206,14 +192,6 @@ Or with auto-reload: npm run watch:start ``` -#### Windows Subsystem for Linux (WSL) - -If using WSL, start PostgreSQL service before running: - -```bash -sudo service postgresql start -``` - ## S3 Storage Structure ``` From 3230f400b965bab02f490b013687e5b5caeb7e2f Mon Sep 17 00:00:00 2001 From: Gabriel Diaz Date: Mon, 29 Dec 2025 12:18:50 -0300 Subject: [PATCH 11/11] chore: Update .env.default structure --- .env.default | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .env.default diff --git a/.env.default b/.env.default new file mode 100644 index 00000000..bb77dcd0 --- /dev/null +++ b/.env.default @@ -0,0 +1,36 @@ +API_VERSION=v1 +AWS_BUCKET_NAME= +AWS_ACCESS_KEY= +AWS_ACCESS_SECRET= +AWS_BUCKET_NAME= +AWS_MAX_FILE_SIZE=26214400 +AWS_STORAGE_URL= +BUILDER_SERVER_URL=https://builder-api.decentraland.org +BUILDER_SHARE_URL=https://share.decentraland.org +BUILDER_URL=https://builder.decentraland.org +CHAIN_NAME=Ethereum Mainnet +COLLECTIONS_GRAPH_URL=https://subgraph.decentraland.org/collections-matic-mainnet +CONNECTION_STRING='postgres://localhost:5432/builder' +DEFAULT_ASSET_PACK_CACHE=86400000 +DEFAULT_ETH_ADDRESS= +DEFAULT_USER_ID= +EXPLORER_URL=https://play.decentraland.org +FORUM_API_KEY= +FORUM_API_USERNAME= +FORUM_CATEGORY= +FORUM_URL=https://forum.decentraland.org +GRAPH_QUERY_TIMEOUT=10000 +IPFS_URL=https://ipfs.infura.io:5001 +IPFS_PROJECT_ID= +IPFS_API_KEY= +OPEN_SEA_URL=https://api.opensea.io/api/v2 +PEER_URL=https://peer.decentraland.org +RPC_URL=https://rpc.decentraland.org/polygon +SERVER_PORT=5000 +THIRD_PARTY_GRAPH_URL=https://subgraph.decentraland.org/tpr-matic-mainnet +CORS_ORIGIN='http://localhost:5173' +CORS_METHOD=* +WAREHOUSE_CONTEXT_PREFIX= +WAREHOUSE_URL= +WAREHOUSE_TOKEN= +WKC_METRICS_BEARER_TOKEN= \ No newline at end of file