Skip to content

Commit

Permalink
Merge pull request #676 from filipedeschamps/parent-root-content
Browse files Browse the repository at this point in the history
Mudança de comportamento no `/root` e exposição do `/parent`
  • Loading branch information
filipedeschamps authored Aug 24, 2022
2 parents f2d24c0 + b8f21e4 commit 47dc3d8
Show file tree
Hide file tree
Showing 8 changed files with 762 additions and 61 deletions.
10 changes: 9 additions & 1 deletion models/authorization.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,15 @@ function filterOutput(user, feature, output) {
}

if (feature === 'read:content') {
filteredOutputValues = validator(output, {
const clonedOutput = { ...output };
if (output.status !== 'published' && user.id !== output.owner_id) {
clonedOutput.title = '[Não disponível]';
clonedOutput.body = '[Não disponível]';
clonedOutput.slug = 'nao-disponivel';
clonedOutput.source_url = null;
}

filteredOutputValues = validator(clonedOutput, {
content: 'required',
});
}
Expand Down
22 changes: 14 additions & 8 deletions models/notification.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
import user from 'models/user.js';
import content from 'models/content.js';
import authorization from 'models/authorization.js';
import webserver from 'infra/webserver.js';
import email from 'infra/email.js';

async function sendReplyEmailToParentUser(createdContent) {
const anonymousUser = user.createAnonymous();
const secureCreatedContent = authorization.filterOutput(anonymousUser, 'read:content', createdContent);

const parentContent = await content.findOne({
where: {
id: createdContent.parent_id,
id: secureCreatedContent.parent_id,
},
});

if (parentContent.owner_id !== createdContent.owner_id) {
if (parentContent.owner_id !== secureCreatedContent.owner_id) {
const parentContentUser = await user.findOneById(parentContent.owner_id);
const childContendUrl = getChildContendUrl(createdContent);
const childContendUrl = getChildContendUrl(secureCreatedContent);
const rootContent = await content.findRootContent({
where: {
id: createdContent.id,
id: secureCreatedContent.id,
},
});

const secureRootContent = authorization.filterOutput(anonymousUser, 'read:content', rootContent);

const subject = getSubject({
createdContent,
rootContent,
createdContent: secureCreatedContent,
rootContent: secureRootContent,
});

const bodyReplyLine = getBodyReplyLine({
createdContent,
rootContent,
createdContent: secureCreatedContent,
rootContent: secureRootContent,
});

await email.send({
Expand Down
88 changes: 66 additions & 22 deletions pages/[username]/[slug]/index.public.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import validator from 'models/validator.js';
import authorization from 'models/authorization.js';
import removeMarkdown from 'models/remove-markdown.js';
import { NotFoundError } from 'errors/index.js';
import { Box } from '@primer/react';
import { Box, Tooltip } from '@primer/react';
import { CommentIcon, CommentDiscussionIcon } from '@primer/octicons-react';
import webserver from 'infra/webserver.js';

export default function Post({
contentFound: contentFoundFallback,
childrenFound: childrenFallback,
contentMetadata,
rootContentFound,
parentContentFound,
contentMetadata,
}) {
const { data: contentFound } = useSWR(
`/api/v1/contents/${contentFoundFallback.owner_username}/${contentFoundFallback.slug}`,
Expand Down Expand Up @@ -73,7 +74,7 @@ export default function Post({
/>
)}
<DefaultLayout metadata={contentMetadata}>
<InReplyToLinks rootContent={rootContentFound} content={contentFound} />
<InReplyToLinks content={contentFound} parentContent={parentContentFound} rootContent={rootContentFound} />

<Box
sx={{
Expand Down Expand Up @@ -128,39 +129,73 @@ export default function Post({
);
}

function InReplyToLinks({ rootContent, content }) {
function InReplyToLinks({ content, parentContent, rootContent }) {
return (
<>
{content.parent_id && rootContent.id === content.parent_id && (
{/* ↱ You are here
[root]->[child]->[child]
*/}
{content.parent_id && parentContent.id === rootContent.id && (
<Box sx={{ fontSize: 1, mb: 3, display: 'flex', flexDirection: 'row' }}>
<Box sx={{ pl: '6px', pr: '14px' }}>
<CommentIcon verticalAlign="middle" size="small" />
</Box>
<Box>
Em resposta a{' '}
<Link href={`/${content.parent_username}/${content.parent_slug}`}>
<strong>
{content.parent_title ? content.parent_title : `/${content.parent_username}/${content.parent_slug}`}
</strong>
</Link>
{parentContent.status === 'published' && (
<Link href={`/${parentContent.owner_username}/${parentContent.slug}`}>
<strong>{parentContent.title}</strong>
</Link>
)}
{parentContent.status !== 'published' && (
<Tooltip
aria-label={`Este conteúdo está atualmente com status "${parentContent.status}"`}
direction="s"
noDelay={true}>
<strong>{parentContent.title}</strong>
</Tooltip>
)}
</Box>
</Box>
)}

{content.parent_id && rootContent.id !== content.parent_id && (
{/* ↱ You are here
[root]->[child]->[child]
*/}
{content.parent_id && parentContent.id !== rootContent.id && (
<Box sx={{ fontSize: 1, mb: 3, display: 'flex', flexDirection: 'row' }}>
<Box sx={{ pl: '7px', pr: '13px' }}>
<CommentDiscussionIcon verticalAlign="middle" size="small" />
</Box>
<Box>
Respondendo a{' '}
<Link href={`/${content.parent_username}/${content.parent_slug}`}>
<strong>este comentário</strong>
</Link>{' '}
{parentContent.status === 'published' && (
<Link href={`/${parentContent.owner_username}/${parentContent.slug}`}>
<strong>"{parentContent.body}..."</strong>{' '}
</Link>
)}
{parentContent.status !== 'published' && (
<Tooltip
aria-label={`Este conteúdo está atualmente com status "${parentContent.status}"`}
direction="s"
noDelay={true}>
<strong>{parentContent.body}</strong>{' '}
</Tooltip>
)}
dentro da publicação{' '}
<Link href={`/${rootContent.owner_username}/${rootContent.slug}`}>
<strong>{rootContent.title}</strong>
</Link>
{rootContent.status === 'published' && (
<Link href={`/${rootContent.owner_username}/${rootContent.slug}`}>
<strong>{rootContent.title}</strong>
</Link>
)}
{rootContent.status !== 'published' && (
<Tooltip
aria-label={`Este conteúdo está atualmente com status "${rootContent.status}"`}
direction="s"
noDelay={true}>
<strong>{rootContent.body}</strong>{' '}
</Tooltip>
)}
</Box>
</Box>
)}
Expand Down Expand Up @@ -292,7 +327,8 @@ export async function getStaticProps(context) {
type: 'article',
};

let secureContentRootFound = null;
let secureRootContentFound = null;
let secureParentContentFound = null;

if (secureContentFound.parent_id) {
const rootContentFound = await content.findRootContent({
Expand All @@ -301,17 +337,25 @@ export async function getStaticProps(context) {
},
});

if (rootContentFound) {
secureContentRootFound = authorization.filterOutput(userTryingToGet, 'read:content', rootContentFound);
}
secureRootContentFound = authorization.filterOutput(userTryingToGet, 'read:content', rootContentFound);

const parentContentFound = await content.findOne({
where: {
id: secureContentFound.parent_id,
},
});

parentContentFound.body = removeMarkdown(parentContentFound.body).replace(/\s+/g, ' ').substring(0, 50);
secureParentContentFound = authorization.filterOutput(userTryingToGet, 'read:content', parentContentFound);
}

return {
props: {
contentFound: JSON.parse(JSON.stringify(secureContentFound)),
childrenFound: JSON.parse(JSON.stringify(secureChildrenList)),
rootContentFound: JSON.parse(JSON.stringify(secureRootContentFound)),
parentContentFound: JSON.parse(JSON.stringify(secureParentContentFound)),
contentMetadata: JSON.parse(JSON.stringify(contentMetadata)),
rootContentFound: JSON.parse(JSON.stringify(secureContentRootFound)),
},
revalidate: 1,
};
Expand Down
70 changes: 70 additions & 0 deletions pages/api/v1/contents/[username]/[slug]/parent/index.public.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import nextConnect from 'next-connect';
import controller from 'models/controller.js';
import user from 'models/user.js';
import authorization from 'models/authorization.js';
import validator from 'models/validator.js';
import content from 'models/content.js';
import { NotFoundError } from 'errors/index.js';

export default nextConnect({
attachParams: true,
onNoMatch: controller.onNoMatchHandler,
onError: controller.onErrorHandler,
})
.use(controller.injectRequestMetadata)
.use(controller.logRequest)
.get(getValidationHandler, getHandler);

function getValidationHandler(request, response, next) {
const cleanValues = validator(request.query, {
username: 'required',
slug: 'required',
});

request.query = cleanValues;

next();
}

async function getHandler(request, response) {
const userTryingToGet = user.createAnonymous();

const contentFound = await content.findOne({
where: {
owner_username: request.query.username,
slug: request.query.slug,
status: 'published',
},
});

if (!contentFound) {
throw new NotFoundError({
message: `O conteúdo informado não foi encontrado no sistema.`,
action: 'Verifique se o "slug" está digitado corretamente.',
stack: new Error().stack,
errorLocationCode: 'CONTROLLER:CONTENT:PARENT:GET_HANDLER:SLUG_NOT_FOUND',
key: 'slug',
});
}

if (contentFound && !contentFound.parent_id) {
throw new NotFoundError({
message: `O conteúdo requisitado é um conteúdo raiz.`,
action:
'Busque apenas por conteúdos com "parent_id", pois este conteúdo não possui níveis superiores na árvore de conteúdos.',
stack: new Error().stack,
errorLocationCode: 'CONTROLLER:CONTENT:PARENT:GET_HANDLER:ALREADY_ROOT',
key: 'parent_id',
});
}

const parentContentFound = await content.findOne({
where: {
id: contentFound.parent_id,
},
});

const secureParentContent = authorization.filterOutput(userTryingToGet, 'read:content', parentContentFound);

return response.status(200).json(secureParentContent);
}
24 changes: 7 additions & 17 deletions pages/api/v1/contents/[username]/[slug]/root/index.public.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,9 @@ async function getHandler(request, response) {
});
}

const rootContentFound = await content.findRootContent({
where: {
id: contentFound.id,
},
});

if (!rootContentFound) {
if (contentFound && !contentFound.parent_id) {
throw new NotFoundError({
message: `O conteúdo requisitado já é o conteúdo raiz.`,
message: `O conteúdo requisitado é um conteúdo raiz.`,
action:
'Busque apenas por conteúdos com "parent_id", pois este conteúdo não possui níveis superiores na árvore de conteúdos.',
stack: new Error().stack,
Expand All @@ -64,15 +58,11 @@ async function getHandler(request, response) {
});
}

if (rootContentFound && rootContentFound.status !== 'published') {
throw new NotFoundError({
message: `O conteúdo raiz não está mais disponível publicamente.`,
action: 'Enquanto o conteúdo raiz não possuir um status público, ele não poderá ser acessado.',
stack: new Error().stack,
errorLocationCode: 'CONTROLLER:CONTENT:ROOT:GET_HANDLER:ROOT_NOT_FOUND',
key: 'status',
});
}
const rootContentFound = await content.findRootContent({
where: {
id: contentFound.id,
},
});

const secureOutputValues = authorization.filterOutput(userTryingToGet, 'read:content', rootContentFound);

Expand Down
Loading

1 comment on commit 47dc3d8

@vercel
Copy link

@vercel vercel bot commented on 47dc3d8 Aug 24, 2022

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-git-main-tabnews.vercel.app
www.tabnews.com.br
tabnews-tabnews.vercel.app
tabnews.com.br

Please sign in to comment.