Skip to content

Commit

Permalink
Merge pull request #1748 from filipedeschamps/feat/filter-content-type
Browse files Browse the repository at this point in the history
Não listar anúncios em relevantes, RSS e `/contents`
  • Loading branch information
aprendendofelipe authored Jul 16, 2024
2 parents 089cebe + e110417 commit 054f47c
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 34 deletions.
26 changes: 6 additions & 20 deletions models/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,7 @@ async function findAll(values = {}, options = {}) {
Object.keys(values.where).forEach((key) => {
if (key === '$not_null') return;

if (key === '$or') {
values.where[key].forEach(($orObject) => {
query.values.push(Object.values($orObject)[0]);
});
} else {
query.values.push(values.where[key]);
}
query.values.push(values.where[key]);
});
}
const results = await database.query(query, { transaction: options.transaction });
Expand All @@ -85,7 +79,6 @@ async function findAll(values = {}, options = {}) {
order: 'optional',
where: 'optional',
count: 'optional',
$or: 'optional',
$not_null: 'optional',
limit: 'optional',
attributes: 'optional',
Expand Down Expand Up @@ -163,18 +156,6 @@ async function findAll(values = {}, options = {}) {
return `contents.${columnName} IS NOT DISTINCT FROM $${globalIndex}`;
}

if (columnName === '$or') {
const $orQuery = columnValue
.map((orColumn) => {
globalIndex += 1;
const orColumnName = Object.keys(orColumn)[0];
return `contents.${orColumnName} = $${globalIndex}`;
})
.join(' OR ');

return `(${$orQuery})`;
}

if (columnName === '$not_null') {
const $notNullQuery = columnValue
.map((notColumnName) => {
Expand All @@ -186,6 +167,11 @@ async function findAll(values = {}, options = {}) {
}

globalIndex += 1;

if (Array.isArray(columnValue)) {
return `contents.${columnName} = ANY ($${globalIndex})`;
}

return `contents.${columnName} = $${globalIndex}`;
}
}, '');
Expand Down
28 changes: 17 additions & 11 deletions models/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,24 +397,38 @@ const schemas = {
let whereSchema = Joi.object({}).optional().min(1);

for (const key of [
'id',
'parent_id',
'slug',
'title',
'body',
'status',
'source_url',
'owner_id',
'username',
'owner_username',
'$or',
'$not_null',
'attributes',
]) {
const keyValidationFunction = schemas[key];
whereSchema = whereSchema.concat(keyValidationFunction());
}

for (const key of ['id', 'status']) {
whereSchema = whereSchema.concat(
Joi.object({
[key]: Joi.alternatives().try(Joi.array().items(schemas[key]().extract(key)), schemas[key]().extract(key)),
}),
);
}

whereSchema = whereSchema.concat(
Joi.object({
type: Joi.alternatives().try(
Joi.array().items(schemas.content_type().extract('type')),
schemas.content_type().extract('type'),
),
}),
);

return Joi.object({
where: whereSchema,
});
Expand All @@ -436,14 +450,6 @@ const schemas = {
});
},

$or: function () {
const statusSchemaWithId = schemas.status().id('status');

return Joi.object({
$or: Joi.array().optional().items(Joi.link('#status')).shared(statusSchemaWithId),
});
},

$not_null: function () {
return Joi.object({
$not_null: Joi.array().optional().items(Joi.string().valid('parent_id')),
Expand Down
2 changes: 1 addition & 1 deletion pages/api/v1/contents/[username]/[slug]/index.public.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ async function patchHandler(request, response) {
where: {
owner_username: request.query.username,
slug: request.query.slug,
$or: [{ status: 'draft' }, { status: 'published' }],
status: ['draft', 'published'],
},
});

Expand Down
1 change: 1 addition & 0 deletions pages/api/v1/contents/index.public.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ async function getHandler(request, response) {
where: {
parent_id: request.query.with_children ? undefined : null,
status: 'published',
type: 'content',
$not_null: request.query.with_root === false ? ['parent_id'] : undefined,
},
attributes: {
Expand Down
1 change: 1 addition & 0 deletions pages/api/v1/contents/rss/index.public.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async function handleRequest(request, response) {
where: {
parent_id: null,
status: 'published',
type: 'content',
},
page: 1,
per_page: 30,
Expand Down
2 changes: 2 additions & 0 deletions queries/rankingQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const rankedContent = `
parent_id IS NULL
AND status = 'published'
AND published_at > NOW() - INTERVAL '7 days'
AND type != 'ad'
UNION
SELECT
contents.id,
Expand All @@ -40,6 +41,7 @@ const rankedContent = `
WHERE
parent_id IS NULL
AND status = 'published'
AND type != 'ad'
),
ranked_published_root_contents AS (
SELECT
Expand Down
1 change: 1 addition & 0 deletions tests/constants-for-tests.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const defaultTabCashForAdCreation = 100;
export const maxSlugLength = 160;
export const maxTitleLength = 255;
export const relevantBody = 'Body with relevant text needs to contain a good amount of words.';
54 changes: 54 additions & 0 deletions tests/integration/api/v1/contents/[username]/get.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import parseLinkHeader from 'parse-link-header';
import { version as uuidVersion } from 'uuid';

import { defaultTabCashForAdCreation, relevantBody } from 'tests/constants-for-tests';
import orchestrator from 'tests/orchestrator.js';

beforeAll(async () => {
Expand Down Expand Up @@ -996,5 +997,58 @@ describe('GET /api/v1/contents/[username]', () => {
`${orchestrator.webserverUrl}/api/v1/contents/${firstUser.username}?strategy=new&with_root=false&page=1&per_page=30`,
);
});

test('"username" existent with "ad" content', async () => {
const firstUser = await orchestrator.createUser();

await orchestrator.createBalance({
balanceType: 'user:tabcash',
recipientId: firstUser.id,
amount: defaultTabCashForAdCreation,
});

const adContent = await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Ad Content',
body: relevantBody,
status: 'published',
type: 'ad',
});

const response = await fetch(`${orchestrator.webserverUrl}/api/v1/contents/${firstUser.username}`);
const responseBody = await response.json();

expect.soft(response.status).toBe(200);

expect(responseBody).toStrictEqual([
{
id: adContent.id,
owner_id: firstUser.id,
parent_id: null,
slug: 'ad-content',
title: 'Ad Content',
status: 'published',
type: 'ad',
source_url: null,
created_at: adContent.created_at.toISOString(),
updated_at: adContent.updated_at.toISOString(),
published_at: adContent.published_at.toISOString(),
deleted_at: null,
owner_username: firstUser.username,
tabcoins: 1,
tabcoins_credit: 0,
tabcoins_debit: 0,
children_deep_count: 0,
},
]);

const responseLinkHeader = parseLinkHeader(response.headers.get('Link'));
expect(responseLinkHeader.first.url).toBe(
`${orchestrator.webserverUrl}/api/v1/contents/${firstUser.username}?strategy=relevant&page=1&per_page=30`,
);
expect(responseLinkHeader.last.url).toBe(
`${orchestrator.webserverUrl}/api/v1/contents/${firstUser.username}?strategy=relevant&page=1&per_page=30`,
);
});
});
});
44 changes: 42 additions & 2 deletions tests/integration/api/v1/contents/get.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import parseLinkHeader from 'parse-link-header';
import { version as uuidVersion } from 'uuid';

import { defaultTabCashForAdCreation, relevantBody } from 'tests/constants-for-tests';
import orchestrator from 'tests/orchestrator.js';
import RequestBuilder from 'tests/request-builder';

beforeAll(async () => {
await orchestrator.waitForAllServices();
await orchestrator.dropAllTables();
await orchestrator.runPendingMigrations();
});

describe('GET /api/v1/contents', () => {
Expand Down Expand Up @@ -1433,5 +1432,46 @@ describe('GET /api/v1/contents', () => {
);
});
});

describe('Should not return "ad" regardless of strategy', () => {
beforeAll(async () => {
await orchestrator.dropAllTables();
await orchestrator.runPendingMigrations();

const firstUser = await orchestrator.createUser();
const secondUser = await orchestrator.createUser();

await orchestrator.createBalance({
balanceType: 'user:tabcash',
recipientId: firstUser.id,
amount: defaultTabCashForAdCreation,
});

const adContent = await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Ad Title',
body: relevantBody,
status: 'published',
type: 'ad',
});

await orchestrator.createContent({
parent_id: adContent.id,
owner_id: secondUser.id,
body: relevantBody,
status: 'published',
});
});

test.each(['', '?strategy=relevant', '?strategy=new', '?strategy=old'])(
'get contents with params: %s',
async (params) => {
const { response, responseBody } = await contentsRequestBuilder.get(params);

expect(response.status).toBe(200);
expect(responseBody).toEqual([]);
},
);
});
});
});
44 changes: 44 additions & 0 deletions tests/integration/api/v1/contents/rss/get.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { defaultTabCashForAdCreation, relevantBody } from 'tests/constants-for-tests';
import orchestrator from 'tests/orchestrator.js';

describe('GET /recentes/rss', () => {
Expand Down Expand Up @@ -45,6 +46,49 @@ describe('GET /recentes/rss', () => {
</rss>`);
});

test('With 1 "ad" content`', async () => {
const defaultUser = await orchestrator.createUser();

await orchestrator.createBalance({
balanceType: 'user:tabcash',
recipientId: defaultUser.id,
amount: defaultTabCashForAdCreation,
});

await orchestrator.createContent({
owner_id: defaultUser.id,
title: 'Ad Title',
body: relevantBody,
status: 'published',
type: 'ad',
});

const response = await fetch(`${orchestrator.webserverUrl}/recentes/rss`);
const responseBody = await response.text();

const lastBuildDateFromResponseBody = /<lastBuildDate>(.*?)<\/lastBuildDate>/.exec(responseBody)[1];

expect(response.status).toEqual(200);

expect(responseBody).toStrictEqual(`<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>TabNews</title>
<link>${orchestrator.webserverUrl}/recentes/rss</link>
<description>Conteúdos para quem trabalha com Programação e Tecnologia</description>
<lastBuildDate>${lastBuildDateFromResponseBody}</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator>https://github.com/jpmonette/feed</generator>
<language>pt</language>
<image>
<title>TabNews</title>
<url>${orchestrator.webserverUrl}/favicon-mobile.png</url>
<link>${orchestrator.webserverUrl}/recentes/rss</link>
</image>
</channel>
</rss>`);
});

test('With 3 contents, 2 `published` and 1 with `draft` status', async () => {
const defaultUser = await orchestrator.createUser();

Expand Down

0 comments on commit 054f47c

Please sign in to comment.