Skip to content

Commit

Permalink
feat: Add more data to terms of services (#762)
Browse files Browse the repository at this point in the history
* feat: Add more ToS events

* fix: Add tests
  • Loading branch information
LautaroPetaccio committed Sep 11, 2024
1 parent ce2015c commit 8c5d92f
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 20 deletions.
93 changes: 93 additions & 0 deletions src/Collection/Collection.router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import {
} from '../SlotUsageCheque'
import { CurationStatus } from '../Curation'
import { isCommitteeMember } from '../Committee'
import * as Warehouse from '../warehouse'
import { app } from '../server'
import { hasPublicAccess } from './access'
import { toFullCollection } from './utils'
Expand All @@ -78,6 +79,7 @@ import {
FullCollection,
CollectionSort,
CollectionTypeFilter,
TermsOfServiceEvent,
} from './Collection.types'

const server = supertest(app.getApp())
Expand All @@ -93,6 +95,7 @@ jest.mock('../Committee')
jest.mock('../Item/Item.model')
jest.mock('./Collection.model')
jest.mock('./access')
jest.mock('../warehouse')

const thirdPartyAPIMock = thirdPartyAPI as jest.Mocked<typeof thirdPartyAPI>
const tpUrnPrefix = 'urn:decentraland:amoy:collections-v2'
Expand Down Expand Up @@ -3097,4 +3100,94 @@ describe('Collection router', () => {
})
})
})

describe('when saving the ToS', () => {
let body: any

beforeEach(() => {
url = `/collections/${dbCollectionMock.id}/tos`
})

describe('and the collection does not exist', () => {
beforeEach(() => {
;(Collection.count as jest.Mock).mockResolvedValueOnce(0)
body = {
email: '[email protected]',
}
})

it('should throw a 404 error', () => {
return server
.post(buildURL(url))
.set(createAuthHeaders('post', url))
.send(body)
.expect(404)
.then((response: any) => {
expect(response.body).toEqual({
data: { id: dbCollection.id, tableName: Collection.tableName },
error: `Couldn't find "${dbCollection.id}" on ${Collection.tableName}`,
ok: false,
})
})
})
})

describe('and the collection exists', () => {
beforeEach(() => {
;(Collection.count as jest.Mock).mockResolvedValueOnce(1)
})

describe("and the ToS don't follow the schema", () => {
beforeEach(() => {
body = {
email: 'This is not an email',
}
})

it('should throw a 400 error', () => {
return server
.post(buildURL(url))
.set(createAuthHeaders('post', url))
.send(body)
.expect(400)
.then((response: any) => {
expect(response.body).toEqual({
data: expect.any(Object),
error: 'Invalid request body',
ok: false,
})
})
})
})

describe('and the ToS is correct', () => {
beforeEach(() => {
;(Collection.findByIds as jest.Mock).mockResolvedValueOnce([
dbCollection,
])
;(Warehouse.sendDataToWarehouse as jest.Mock).mockResolvedValueOnce(
undefined
)
body = {
email: '[email protected]',
event: TermsOfServiceEvent.PUBLISH_THIRD_PARTY_ITEMS,
hashes: ['hash1', 'hash2'],
}
})

it('should save the ToS and return a 200', () => {
return server
.post(buildURL(url))
.set(createAuthHeaders('post', url))
.send(body)
.expect(200)
.then((response: any) => {
expect(response.body).toEqual({
ok: true,
})
})
})
})
})
})
})
61 changes: 43 additions & 18 deletions src/Collection/Collection.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { omit } from 'decentraland-commons/dist/utils'
import { withCors } from '../middleware/cors'
import { Router } from '../common/Router'
import { HTTPError, STATUS_CODES } from '../common/HTTPError'
import { getValidator } from '../utils/validator'
import { InvalidRequestError } from '../utils/errors'
import {
withModelAuthorization,
Expand Down Expand Up @@ -39,7 +38,11 @@ import {
} from '../Pagination/utils'
import { CurationStatusFilter } from '../Curation'
import { addCustomMaxAgeCacheControlHeader } from '../common/headers'
import { hasTPCollectionURN, isTPCollection } from '../utils/urn'
import {
getThirdPartyCollectionURN,
hasTPCollectionURN,
isTPCollection,
} from '../utils/urn'
import { ForumService } from '../Forum/Forum.service'
import { Collection } from './Collection.model'
import { CollectionService } from './Collection.service'
Expand All @@ -49,6 +52,7 @@ import {
FullCollection,
CollectionTypeFilter,
CollectionSort,
TermsOfServiceEvent,
} from './Collection.types'
import { upsertCollectionSchema, saveTOSSchema } from './Collection.schema'
import { hasPublicAccess } from './access'
Expand All @@ -64,8 +68,6 @@ import {
WrongCollectionError,
} from './Collection.errors'

const validator = getValidator()

export class CollectionRouter extends Router {
public service = new CollectionService()
public forumService = new ForumService()
Expand Down Expand Up @@ -150,6 +152,7 @@ export class CollectionRouter extends Router {
withCors,
withAuthentication,
withCollectionExists,
withSchemaValidation(saveTOSSchema),
server.handleRequest(this.saveTOS)
)

Expand Down Expand Up @@ -478,23 +481,45 @@ export class CollectionRouter extends Router {
}

saveTOS = async (req: AuthRequest): Promise<void> => {
const tosValidator = validator.compile(saveTOSSchema)
tosValidator(req.body)
if (tosValidator.errors) {
throw new HTTPError(
'Invalid request',
tosValidator.errors,
STATUS_CODES.badRequest
)
const id = server.extractFromReq(req, 'id')
const collection = await this.service.getCollection(id)
const collection_address = collection.contract_address ?? 'Unknown address'
const eth_address = req.auth.ethAddress
const urn =
collection.third_party_id && collection.urn_suffix
? getThirdPartyCollectionURN(
collection.third_party_id,
collection.urn_suffix
)
: 'Unknown urn'
const event = req.body.event ?? TermsOfServiceEvent.PUBLISH_COLLECTION

let body:
| ({ email: string; eth_address: string } & {
collection_address: string
})
| { urn: string; hashes: string[] }

switch (event) {
case TermsOfServiceEvent.PUBLISH_THIRD_PARTY_ITEMS:
body = {
email: req.body.email,
urn,
hashes: req.body.hashes,
}
break
case TermsOfServiceEvent.PUBLISH_COLLECTION:
default:
body = {
email: req.body.email,
eth_address,
collection_address,
}
break
}

const eth_address = req.auth.ethAddress
try {
await sendDataToWarehouse('builder', 'publish_collection_tos', {
email: req.body.email,
eth_address: eth_address,
collection_address: req.body.collection_address,
})
await sendDataToWarehouse('builder', event, body)
} catch (e) {
throw new HTTPError(
"The TOS couldn't be recorded",
Expand Down
18 changes: 16 additions & 2 deletions src/Collection/Collection.schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ContractNetwork } from '@dcl/schemas'
import { matchers } from '../common/matchers'
import { TermsOfServiceEvent } from './Collection.types'

export const collectionSchema = Object.freeze({
type: 'object',
Expand Down Expand Up @@ -59,8 +60,21 @@ export const saveTOSSchema = Object.freeze({
type: 'string',
pattern: `^${matchers.email}$`,
},
collection_address: { type: 'string', pattern: `^${matchers.address}$` },
collection_address: { type: 'string' },
event: {
type: 'string',
enum: [
TermsOfServiceEvent.PUBLISH_COLLECTION,
TermsOfServiceEvent.PUBLISH_THIRD_PARTY_ITEMS,
],
},
hashes: {
type: 'array',
items: { type: 'string' },
minItems: 1,
maxItems: 40000,
},
},
required: ['email'],
additionalProperties: false,
required: ['email', 'collection_address'],
})
5 changes: 5 additions & 0 deletions src/Collection/Collection.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export type CollectionAttributes = {
updated_at: Date
}

export enum TermsOfServiceEvent {
PUBLISH_COLLECTION = 'publish_collection_tos',
PUBLISH_THIRD_PARTY_ITEMS = 'publish_third_party_items_tos',
}

export type ThirdPartyCollectionAttributes = CollectionAttributes & {
third_party_id: string
urn_suffix: string
Expand Down

0 comments on commit 8c5d92f

Please sign in to comment.