Skip to content

Commit

Permalink
Merge pull request #1188 from ErickCReis/feat/profile-tabs
Browse files Browse the repository at this point in the history
Feature (API) - Adiciona query params para filtrar lista de conteúdos do usuário
  • Loading branch information
aprendendofelipe authored Dec 18, 2023
2 parents 4e4d030 + 08c54b6 commit 397dc7a
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 0 deletions.
13 changes: 13 additions & 0 deletions models/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ async function findAll(values = {}, options = {}) {

if (values.where) {
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]);
Expand Down Expand Up @@ -83,6 +85,7 @@ async function findAll(values = {}, options = {}) {
where: 'optional',
count: 'optional',
$or: 'optional',
$not_null: 'optional',
limit: 'optional',
attributes: 'optional',
});
Expand Down Expand Up @@ -167,6 +170,16 @@ async function findAll(values = {}, options = {}) {
return `(${$orQuery})`;
}

if (columnName === '$not_null') {
const $notNullQuery = columnValue
.map((notColumnName) => {
return `contents.${notColumnName} IS NOT NULL`;
})
.join(' AND ');

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

globalIndex += 1;
return `contents.${columnName} = $${globalIndex}`;
}
Expand Down
36 changes: 36 additions & 0 deletions models/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ const schemas = {
'username',
'owner_username',
'$or',
'$not_null',
'attributes',
]) {
const keyValidationFunction = schemas[key];
Expand Down Expand Up @@ -566,6 +567,15 @@ const schemas = {
});
},

$not_null: function () {
return Joi.object({
$not_null: Joi.array().optional().items(Joi.string().valid('parent_id')).messages({
'array.base': `"#not_null" deve ser do tipo Array.`,
'any.only': `"#not_null" deve conter um dos seguintes valores: "parent_id".`,
}),
});
},

attributes: function () {
return Joi.object({
attributes: Joi.object({
Expand Down Expand Up @@ -635,6 +645,32 @@ const schemas = {
return contentSchema;
},

with_children: function () {
return Joi.object({
with_children: Joi.boolean()
.allow(false)
.when('$required.with_children', { is: 'required', then: Joi.required(), otherwise: Joi.optional() })
.messages({
'any.required': `"with_children" é um campo obrigatório.`,
'string.empty': `"with_children" não pode estar em branco.`,
'boolean.base': `"with_children" deve ser do tipo Boolean.`,
}),
});
},

with_root: function () {
return Joi.object({
with_root: Joi.boolean()
.allow(false)
.when('$required.with_root', { is: 'required', then: Joi.required(), otherwise: Joi.optional() })
.messages({
'any.required': `"with_root" é um campo obrigatório.`,
'string.empty': `"with_root" não pode estar em branco.`,
'boolean.base': `"with_root" deve ser do tipo Boolean.`,
}),
});
},

event: function () {
return Joi.object({
type: Joi.string()
Expand Down
4 changes: 4 additions & 0 deletions pages/api/v1/contents/[username]/index.public.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ function getValidationHandler(request, response, next) {
page: 'optional',
per_page: 'optional',
strategy: 'optional',
with_root: 'optional',
with_children: 'optional',
});

request.query = cleanValues;
Expand All @@ -37,8 +39,10 @@ async function getHandler(request, response) {
const results = await content.findWithStrategy({
strategy: request.query.strategy,
where: {
parent_id: request.query.with_children === false ? null : undefined,
owner_username: request.query.username,
status: 'published',
$not_null: request.query.with_root === false ? ['parent_id'] : undefined,
},
page: request.query.page,
per_page: request.query.per_page,
Expand Down
191 changes: 191 additions & 0 deletions tests/integration/api/v1/contents/[username]/get.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,5 +677,196 @@ describe('GET /api/v1/contents/[username]', () => {
expect(page2ResponseBody[28].title).toEqual('Conteúdo #2');
expect(page2ResponseBody[29].title).toEqual('Conteúdo #1');
});

test('"username" existent with 4 contents, but only 2 "root" "published" and with_children "false"', async () => {
const firstUser = await orchestrator.createUser();
const secondUser = await orchestrator.createUser();

const firstRootContent = await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Primeiro conteúdo criado',
status: 'published',
});

const secondRootContent = await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Segundo conteúdo criado',
status: 'published',
});

await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Terceiro conteúdo criado',
body: `Este conteúdo não deverá aparecer na lista retornada pelo /contents/[username],
porque quando um conteúdo possui o "status" como "draft", ele não
esta pronto para ser listado publicamente.`,
status: 'draft',
});

await orchestrator.createContent({
owner_id: firstUser.id,
parent_id: firstRootContent.id,
title: 'Quarto conteúdo criado',
body: `Este conteúdo não deverá aparecer na lista retornada pelo /contents/[username],
porque o parâmetro "with_children" foi passado como "false".`,
status: 'published',
});

await orchestrator.createContent({
owner_id: secondUser.id,
title: 'Conteúdo de outro usuário',
status: 'published',
});

const response = await fetch(
`${orchestrator.webserverUrl}/api/v1/contents/${firstUser.username}?strategy=new&with_children=false`
);
const responseBody = await response.json();

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

expect(responseBody).toStrictEqual([
{
id: secondRootContent.id,
owner_id: firstUser.id,
parent_id: null,
slug: 'segundo-conteudo-criado',
title: 'Segundo conteúdo criado',
status: 'published',
source_url: null,
created_at: secondRootContent.created_at.toISOString(),
updated_at: secondRootContent.updated_at.toISOString(),
published_at: secondRootContent.published_at.toISOString(),
deleted_at: null,
tabcoins: 1,
owner_username: firstUser.username,
children_deep_count: 0,
},
{
id: firstRootContent.id,
owner_id: firstUser.id,
parent_id: null,
slug: 'primeiro-conteudo-criado',
title: 'Primeiro conteúdo criado',
status: 'published',
source_url: null,
created_at: firstRootContent.created_at.toISOString(),
updated_at: firstRootContent.updated_at.toISOString(),
published_at: firstRootContent.published_at.toISOString(),
deleted_at: null,
tabcoins: 1,
owner_username: firstUser.username,
children_deep_count: 1,
},
]);

expect(uuidVersion(responseBody[0].id)).toEqual(4);
expect(uuidVersion(responseBody[1].id)).toEqual(4);
expect(uuidVersion(responseBody[0].owner_id)).toEqual(4);
expect(uuidVersion(responseBody[1].owner_id)).toEqual(4);
expect(responseBody[0].published_at > responseBody[1].published_at).toEqual(true);
});

test('"username" existent with 5 contents, but only 2 "root" "published", and with_root "false"', async () => {
const firstUser = await orchestrator.createUser();
const secondUser = await orchestrator.createUser();

const firstRootContent = await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Primeiro conteúdo criado',
body: `Este conteúdo não deverá aparecer na lista retornada pelo /contents/[username],
porque o parâmetro "with_root" foi passado como "false".`,
status: 'published',
});

await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Segundo conteúdo criado',
body: `Este conteúdo não deverá aparecer na lista retornada pelo /contents/[username],
porque o parâmetro "with_root" foi passado como "false".`,
status: 'published',
});

await orchestrator.createContent({
owner_id: firstUser.id,
title: 'Terceiro conteúdo criado',
body: `Este conteúdo não deverá aparecer na lista retornada pelo /contents/[username],
porque quando um conteúdo possui o "status" como "draft", ele não
esta pronto para ser listado publicamente.`,
status: 'draft',
});

const firstChildContent = await orchestrator.createContent({
owner_id: firstUser.id,
parent_id: firstRootContent.id,
title: 'Quarto conteúdo criado',
body: `Este conteúdo deverá aparecer na lista retornada pelo /contents/[username]`,
status: 'published',
});

const secondChildContent = await orchestrator.createContent({
owner_id: firstUser.id,
parent_id: firstRootContent.id,
title: 'Quinto conteúdo criado',
body: `Este conteúdo deverá aparecer na lista retornada pelo /contents/[username]`,
status: 'published',
});

await orchestrator.createContent({
owner_id: secondUser.id,
title: 'Conteúdo de outro usuário',
status: 'published',
});

const response = await fetch(
`${orchestrator.webserverUrl}/api/v1/contents/${firstUser.username}?strategy=new&with_root=false`
);
const responseBody = await response.json();

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

expect(responseBody).toStrictEqual([
{
id: secondChildContent.id,
owner_id: firstUser.id,
parent_id: firstRootContent.id,
slug: 'quinto-conteudo-criado',
title: 'Quinto conteúdo criado',
body: 'Este conteúdo deverá aparecer na lista retornada pelo /contents/[username]',
status: 'published',
source_url: null,
created_at: secondChildContent.created_at.toISOString(),
updated_at: secondChildContent.updated_at.toISOString(),
published_at: secondChildContent.published_at.toISOString(),
deleted_at: null,
owner_username: firstUser.username,
tabcoins: 0,
children_deep_count: 0,
},
{
id: firstChildContent.id,
owner_id: firstUser.id,
parent_id: firstRootContent.id,
slug: 'quarto-conteudo-criado',
title: 'Quarto conteúdo criado',
body: 'Este conteúdo deverá aparecer na lista retornada pelo /contents/[username]',
status: 'published',
source_url: null,
created_at: firstChildContent.created_at.toISOString(),
updated_at: firstChildContent.updated_at.toISOString(),
published_at: firstChildContent.published_at.toISOString(),
deleted_at: null,
owner_username: firstUser.username,
tabcoins: 0,
children_deep_count: 0,
},
]);

expect(uuidVersion(responseBody[0].id)).toEqual(4);
expect(uuidVersion(responseBody[1].id)).toEqual(4);
expect(uuidVersion(responseBody[0].owner_id)).toEqual(4);
expect(uuidVersion(responseBody[1].owner_id)).toEqual(4);
expect(responseBody[0].published_at > responseBody[1].published_at).toEqual(true);
});
});
});

1 comment on commit 397dc7a

@vercel
Copy link

@vercel vercel bot commented on 397dc7a Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

tabnews – ./

tabnews-tabnews.vercel.app
tabnews-git-main-tabnews.vercel.app
www.tabnews.com.br
tabnews.com.br

Please sign in to comment.