Skip to content

Commit

Permalink
feat: Get utility from the Builder's DB
Browse files Browse the repository at this point in the history
  • Loading branch information
LautaroPetaccio committed Jun 5, 2024
1 parent 8628635 commit 3b3c0eb
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 55 deletions.
1 change: 0 additions & 1 deletion .env.defaults
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
CORS_ORIGIN=^http:\/\/localhost:[0-9]{1,10}$
CORS_METHOD=*
BUILDER_SERVER_URL=https://builder-api.decentraland.org
MARKETPLACE_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/decentraland/marketplace
COLLECTIONS_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/decentraland/collections-matic-mainnet
RENTALS_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/decentraland/rentals-ethereum-mainnet
Expand Down
6 changes: 5 additions & 1 deletion .env.spec
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
CORS_ORIGIN=*
CORS_METHOD=*
BUILDER_SERVER_URL=https://builder-api.decentraland.org
MARKETPLACE_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/decentraland/marketplace
COLLECTIONS_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/decentraland/collections-matic-mainnet
RENTALS_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/decentraland/rentals-ethereum-mainnet
SIGNATURES_SERVER_URL=http://localhost:3000
MARKETPLACE_FAVORITES_SERVER_URL=http://localhost:3000
BUILDER_PG_COMPONENT_PSQL_DATABASE=builder
BUILDER_PG_COMPONENT_PSQL_PORT=8020
BUILDER_PG_COMPONENT_PSQL_HOST=localhost
BUILDER_PG_COMPONENT_PSQL_USER=user
BUILDER_PG_COMPONENT_PSQL_PASSWORD=password
MARKETPLACE_CHAIN_ID=3
COLLECTIONS_CHAIN_ID=137

Expand Down
40 changes: 33 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,6 @@ async function initComponents(): Promise<AppComponents> {
const marketplaceChainId = getMarketplaceChainId()
const collectionsChainId = getCollectionsChainId()

// Builder component
const builder = createBuilderComponent({
url: await config.requireString('BUILDER_SERVER_URL'),
logs,
fetcher: fetch,
})

// subgraphs
const marketplaceSubgraph = await createSubgraphComponent(
{ logs, config, fetch, metrics },
Expand All @@ -260,8 +253,41 @@ async function initComponents(): Promise<AppComponents> {
await config.requireString('RENTALS_SUBGRAPH_URL')
)

// Builder the Builder's DB connection string

const builderDbName = await config.requireString(
'BUILDER_PG_COMPONENT_PSQL_DATABASE'
)
const builderDbPort = await config.requireString(
'BUILDER_PG_COMPONENT_PSQL_PORT'
)
const builderDbHost = await config.requireString(
'BUILDER_PG_COMPONENT_PSQL_HOST'
)
const builderDbUser = await config.requireString(
'BUILDER_PG_COMPONENT_PSQL_USER'
)
const builderDbPassword = await config.requireString(
'BUILDER_PG_COMPONENT_PSQL_PASSWORD'
)
const builderDbConnectionString = `postgresql://${builderDbUser}:${builderDbPassword}@${builderDbHost}:${builderDbPort}/${builderDbName}`

// dbs
const satsumaDatabase = await createPgComponent({ config, logs, metrics })
const builderDatabase = await createPgComponent(
{ config, logs, metrics },
{
pool: {
connectionString: builderDbConnectionString,
},
}
)

// Builder component
const builder = createBuilderComponent({
database: builderDatabase,
logs,
})

// orders
const marketplaceOrders = createOrdersComponent({
Expand Down
31 changes: 14 additions & 17 deletions src/ports/builder/component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { URL } from 'url'
import {
IFetchComponent,
ILoggerComponent,
} from '@well-known-components/interfaces'
import SQL from 'sql-template-strings'
import { ILoggerComponent } from '@well-known-components/interfaces'
import { IBuilderComponent } from './types'
import { IPgComponent } from '@well-known-components/pg-component'

export function createBuilderComponent(options: {
url: string
logs: ILoggerComponent
fetcher: IFetchComponent
database: IPgComponent
}): IBuilderComponent {
const { fetcher, url, logs } = options
const { database, logs } = options
const logger = logs.getLogger('builder-component')

return {
Expand All @@ -19,18 +16,18 @@ export function createBuilderComponent(options: {
itemId: string
): Promise<string | undefined> {
try {
const baseUrl = new URL(url)
baseUrl.pathname = `/v1/published-collections/${collectionAddress}/items/${itemId}/utility`
const response = await fetcher.fetch(baseUrl.toString())
if (!response.ok) {
const query = SQL`SELECT items.utility
FROM items
INNER JOIN collections ON items.collection_id = collections.id
WHERE items.blockchain_item_id = ${itemId} AND collections.contract_address = ${collectionAddress}`

const result = await database.query<{ utility: string | null }>(query)
if (!result.rowCount) {
throw new Error(
`Failed to fetch utility for item: ${response.status}`
'Failed getting the utility for the item: the item was not found in the DB'
)
}
const utility = (await response.json()) as {
data: { utility: string | null }
}
return utility.data.utility ?? undefined
return result.rows[0].utility ?? undefined
} catch (_e) {
logger.info(
`Failed looking for the utility of the item: ${collectionAddress} - ${itemId}.`
Expand Down
7 changes: 3 additions & 4 deletions src/tests/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,11 +377,10 @@ export async function initComponents(): Promise<AppComponents> {
)

// Builder component
const BUILDER_SERVER_URL = await config.requireString('BUILDER_SERVER_URL')

const builder = createBuilderComponent({
fetcher: fetchComponent,
logs,
url: BUILDER_SERVER_URL,
database: createTestDbComponent(),
logs: logs,
})

// items
Expand Down
52 changes: 27 additions & 25 deletions src/tests/ports/builder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IBuilderComponent, createBuilderComponent } from '../../ports/builder'
import { createTestDbComponent } from '../components'

let builderComponent: IBuilderComponent
let fetchMock: jest.Mock
const builderUrl = 'http://example.com'
let queryMock: jest.Mock

describe('when getting the wearable utility', () => {
let blockchainItemId: string
Expand All @@ -11,9 +11,8 @@ describe('when getting the wearable utility', () => {
beforeEach(() => {
blockchainItemId = '1'
collectionAddress = '0x123'
fetchMock = jest.fn()
queryMock = jest.fn()
builderComponent = createBuilderComponent({
url: builderUrl,
logs: {
getLogger: () => ({
info: jest.fn(),
Expand All @@ -23,45 +22,48 @@ describe('when getting the wearable utility', () => {
debug: jest.fn(),
}),
},
fetcher: {
fetch: fetchMock,
},
database: createTestDbComponent({ query: queryMock }),
})
})

describe('when performing the request', () => {
beforeEach(() => {
fetchMock.mockResolvedValueOnce({
ok: true,
json: async () => ({ data: { utility: 'This is a utility' } }),
queryMock.mockResolvedValueOnce({
rows: [{ utility: 'This is a utility' }],
rowCount: 1,
})
})

it('should query the utility endpoint with the contract address and item id', async () => {
it('should query the builder DB with the contract address and item id', async () => {
expect(
await builderComponent.getItemUtility(
collectionAddress,
blockchainItemId
)
).toEqual(expect.anything())
expect(fetchMock).toHaveBeenCalledWith(
`${builderUrl}/v1/published-collections/${collectionAddress}/items/${blockchainItemId}/utility`

const firstCall = queryMock.mock.calls[0][0]
expect(firstCall.text).toMatch(
'WHERE items.blockchain_item_id = $1 AND collections.contract_address = $2'
)
expect(firstCall.values).toEqual([blockchainItemId, collectionAddress])
})
})

describe('and the request is successful', () => {
describe('and the query returns a row', () => {
let data: { utility: string | null }

beforeEach(() => {
fetchMock.mockResolvedValueOnce({
ok: true,
json: async () => ({ data }),
data = { utility: null }
queryMock.mockResolvedValueOnce({
rows: [data],
rowCount: 1,
})
})

describe('and the utility is null', () => {
beforeEach(() => {
data = { utility: null }
data.utility = null
})

it('should resolve to undefined', async () => {
Expand All @@ -76,7 +78,7 @@ describe('when getting the wearable utility', () => {

describe('and the utility is not null', () => {
beforeEach(() => {
data = { utility: 'This is a utility' }
data.utility = 'This is a utility'
})

it('should resolve to the utility', async () => {
Expand All @@ -90,11 +92,11 @@ describe('when getting the wearable utility', () => {
})
})

describe("and the request fails with a status different than a 200's", () => {
describe('and the query returns no rows', () => {
beforeEach(() => {
fetchMock.mockResolvedValueOnce({
ok: false,
status: 404,
queryMock.mockResolvedValueOnce({
rows: [],
rowCount: 0,
})
})

Expand All @@ -108,9 +110,9 @@ describe('when getting the wearable utility', () => {
})
})

describe('and the request fails', () => {
describe('and the query fails', () => {
beforeEach(() => {
fetchMock.mockRejectedValueOnce(new Error('An error occurred'))
queryMock.mockRejectedValueOnce(new Error('An error occurred'))
})

it('should resolve to undefined', async () => {
Expand Down

0 comments on commit 3b3c0eb

Please sign in to comment.