diff --git a/models/advertisement.js b/models/advertisement.js index 139f38128..20e216d66 100644 --- a/models/advertisement.js +++ b/models/advertisement.js @@ -1,10 +1,22 @@ import database from 'infra/database'; -async function getRandom(limit) { +async function getRandom(limit, options = {}) { + const { ignoreId, ownerId, tryOtherOwners } = options; + const query = { values: [limit], }; + let where = "type = 'ad' AND status = 'published'"; + + if (ownerId) { + where += ` AND c.owner_id = '${ownerId}'`; + } + + if (ignoreId) { + where += ` AND c.id != '${ignoreId}'`; + } + query.text = ` SELECT c.id, @@ -15,13 +27,17 @@ async function getRandom(limit) { 'markdown' as ad_type FROM contents c INNER JOIN users u ON c.owner_id = u.id - WHERE type = 'ad' AND status = 'published' + WHERE ${where} ORDER BY RANDOM() LIMIT $1; `; const results = await database.query(query); + if (!results.rows.length && ownerId && tryOtherOwners) { + return getRandom(limit, { ignoreId }); + } + return results.rows; } diff --git a/models/validator.js b/models/validator.js index 0f85d542e..5b1926fe9 100644 --- a/models/validator.js +++ b/models/validator.js @@ -393,6 +393,20 @@ const schemas = { }); }, + ignore_id: function () { + return Joi.object({ + ignore_id: schemas.id().extract('id'), + }); + }, + + flexible: function () { + return Joi.object({ + flexible: Joi.boolean() + .default(false) + .when('$required.optional', { is: 'required', then: Joi.required(), otherwise: Joi.optional() }), + }); + }, + where: function () { let whereSchema = Joi.object({}).optional().min(1); diff --git a/pages/[username]/[slug]/index.public.js b/pages/[username]/[slug]/index.public.js index b03112dd1..fe654f71a 100644 --- a/pages/[username]/[slug]/index.public.js +++ b/pages/[username]/[slug]/index.public.js @@ -418,7 +418,11 @@ export const getStaticProps = getStaticPropsRevalidate(async (context) => { secureParentContentFound.body = removeMarkdown(secureParentContentFound.body, { maxLength: 50 }); } - const adsFound = await ad.getRandom(1); + const adsFound = await ad.getRandom(1, { + ignoreId: secureContentFound.id, + ownerId: secureContentFound.owner_id, + tryOtherOwners: secureContentFound.type === 'content', + }); const secureAdValues = authorization.filterOutput(userTryingToGet, 'read:ad:list', adsFound); return { diff --git a/pages/api/v1/sponsored-beta/index.public.js b/pages/api/v1/sponsored-beta/index.public.js index 6112cf03a..d5ff3b189 100644 --- a/pages/api/v1/sponsored-beta/index.public.js +++ b/pages/api/v1/sponsored-beta/index.public.js @@ -19,6 +19,9 @@ export default nextConnect({ function getValidationHandler(request, response, next) { const cleanValues = validator(request.query, { per_page: 'optional', + owner_id: 'optional', + ignore_id: 'optional', + flexible: 'optional', }); request.query = cleanValues; @@ -29,7 +32,11 @@ function getValidationHandler(request, response, next) { async function getHandler(request, response) { const userTryingToList = user.createAnonymous(); - const ads = await ad.getRandom(request.query.per_page); + const ads = await ad.getRandom(request.query.per_page, { + ignoreId: request.query.ignore_id, + ownerId: request.query.owner_id, + tryOtherOwners: request.query.flexible, + }); const secureOutputValues = authorization.filterOutput(userTryingToList, 'read:ad:list', ads); diff --git a/tests/integration/api/v1/sponsored-beta/get.test.js b/tests/integration/api/v1/sponsored-beta/get.test.js index 997e2ebd5..bf84b046a 100644 --- a/tests/integration/api/v1/sponsored-beta/get.test.js +++ b/tests/integration/api/v1/sponsored-beta/get.test.js @@ -84,12 +84,64 @@ describe('GET /api/v1/sponsored-beta', () => { expect(createdAds).toContainEqual(responseBody[0]); expect(createdAds).toContainEqual(responseBody[1]); }); + + it('should ignore specific ad', async () => { + const createdAds = await createAds(1, owner); + + const { response, responseBody } = await adsRequestBuilder.get(`?ignore_id=${createdAds[0].id}`); + + expect.soft(response.status).toBe(200); + expect(responseBody).toEqual([]); + }); + + it('should get from specific owner', async () => { + const specificOwner = await orchestrator.createUser(); + + orchestrator.createBalance({ + balanceType: 'user:tabcash', + recipientId: specificOwner.id, + amount: defaultTabCashForAdCreation, + }); + + await createAds(10, owner); + const specificAd = await createAds(1, specificOwner); + await createAds(10, owner, 10); + + const { response, responseBody } = await adsRequestBuilder.get(`?owner_id=${specificOwner.id}`); + + expect.soft(response.status).toBe(200); + expect(responseBody).toStrictEqual(specificAd); + }); + + it('should try get from another owner', async () => { + const specificOwner = await orchestrator.createUser(); + + const createdAds = await createAds(1, owner); + + const { response, responseBody } = await adsRequestBuilder.get(`?flexible=true&owner_id=${specificOwner.id}`); + + expect.soft(response.status).toBe(200); + expect(responseBody).toStrictEqual(createdAds); + }); + + it('should try get from another owner and ignore specific ad', async () => { + const specificOwner = await orchestrator.createUser(); + + const createdAds = await createAds(2, owner); + + const { response, responseBody } = await adsRequestBuilder.get( + `?flexible=true&owner_id=${specificOwner.id}&ignore_id=${createdAds[1].id}`, + ); + + expect.soft(response.status).toBe(200); + expect(responseBody).toStrictEqual([createdAds[0]]); + }); }); }); -async function createAds(count, owner) { +async function createAds(count, owner, indexOffset = 0) { const ads = []; - for (let i = 0; i < count; i++) { + for (let i = indexOffset; i < count + indexOffset; i++) { const ad = await orchestrator.createContent({ owner_id: owner.id, title: `Ad #${i}`,