diff --git a/models/content.js b/models/content.js index f92e10de7..25329aa72 100644 --- a/models/content.js +++ b/models/content.js @@ -273,20 +273,14 @@ async function create(postedContent, options = {}) { checkRootContentTitle(validContent); - const parentContent = validContent.parent_id - ? await checkIfParentIdExists(validContent, { - transaction: options.transaction, - }) - : null; - - injectIdAndPath(validContent, parentContent); - populatePublishedAtValue(null, validContent); const newContent = await runInsertQuery(validContent, { transaction: options.transaction, }); + throwIfSpecifiedParentDoesNotExist(postedContent, newContent); + await creditOrDebitTabCoins(null, newContent, { eventId: options.eventId, transaction: options.transaction, @@ -310,10 +304,22 @@ async function create(postedContent, options = {}) { const query = { text: ` WITH + parent AS ( + SELECT + owner_id, + CASE + WHEN id IS NULL THEN ARRAY[]::uuid[] + ELSE ARRAY_APPEND(path, id) + END AS child_path + FROM (SELECT 1) AS dummy + LEFT JOIN contents + ON id = $2 + ), inserted_content as ( INSERT INTO contents (id, parent_id, owner_id, slug, title, body, status, source_url, published_at, path) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, parent.child_path + FROM parent RETURNING * ) SELECT @@ -330,11 +336,14 @@ async function create(postedContent, options = {}) { inserted_content.published_at, inserted_content.deleted_at, inserted_content.path, - users.username as owner_username + users.username as owner_username, + parent.owner_id as parent_owner_id FROM inserted_content INNER JOIN users ON inserted_content.owner_id = users.id + LEFT JOIN + parent ON true ;`, values: [ content.id, @@ -346,7 +355,6 @@ async function create(postedContent, options = {}) { content.status, content.source_url, content.published_at, - content.path, ], }; @@ -359,18 +367,6 @@ async function create(postedContent, options = {}) { } } -function injectIdAndPath(validContent, parentContent) { - validContent.id = uuidV4(); - - if (parentContent) { - validContent.path = [...parentContent.path, parentContent.id]; - } - - if (!parentContent) { - validContent.path = []; - } -} - function populateSlug(postedContent) { if (!postedContent.slug) { postedContent.slug = getSlug(postedContent.title) || uuidV4(); @@ -405,17 +401,10 @@ function populateStatus(postedContent) { postedContent.status = postedContent.status || 'draft'; } -async function checkIfParentIdExists(content, options) { - const existingContent = await findOne( - { - where: { - id: content.parent_id, - }, - }, - options, - ); +function throwIfSpecifiedParentDoesNotExist(postedContent, newContent) { + const existingParentId = newContent.path.at(-1); - if (!existingContent) { + if (postedContent.parent_id && postedContent.parent_id !== existingParentId) { throw new ValidationError({ message: `Você está tentando criar um comentário em um conteúdo que não existe.`, action: `Utilize um "parent_id" que aponte para um conteúdo existente.`, @@ -425,8 +414,6 @@ async function checkIfParentIdExists(content, options) { key: 'parent_id', }); } - - return existingContent; } function parseQueryErrorToCustomError(error) { @@ -446,6 +433,7 @@ function parseQueryErrorToCustomError(error) { function validateCreateSchema(content) { const cleanValues = validator(content, { + id: 'required', parent_id: 'optional', owner_id: 'required', slug: 'required', @@ -558,23 +546,25 @@ async function creditOrDebitTabCoins(oldContent, newContent, options = {}) { }); } + // We should not credit if the content has little or no value. + if (newContent.body.split(/[a-z]{5,}/i, 6).length < 6) return; + if (newContent.parent_id) { - const parentContent = await findOne( - { - where: { - id: newContent.parent_id, - }, - }, - options, - ); + let parentOwnerId = newContent.parent_owner_id; + + if (parentOwnerId === undefined) { + const queryParent = { + text: `SELECT owner_id FROM contents WHERE id = $1;`, + values: [newContent.parent_id], + }; + const parentQueryResult = await database.query(queryParent, options); + const parentContent = parentQueryResult.rows[0]; + parentOwnerId = parentContent.owner_id; + } // We should not credit if the parent content is from the same user. - if (parentContent.owner_id === newContent.owner_id) return; + if (parentOwnerId === newContent.owner_id) return; } - - // We should not credit if the content has little or no value. - // Expected 5 or more words with 5 or more characters. - if (newContent.body.split(/[a-z]{5,}/i, 6).length < 6) return; } if (userEarnings > 0) { @@ -611,14 +601,16 @@ async function creditOrDebitTabCoins(oldContent, newContent, options = {}) { async function update(contentId, postedContent, options = {}) { const validPostedContent = validateUpdateSchema(postedContent); - const oldContent = await findOne( - { - where: { - id: contentId, + const oldContent = + options.oldContent ?? + (await findOne( + { + where: { + id: contentId, + }, }, - }, - options, - ); + options, + )); const newContent = { ...oldContent, ...validPostedContent }; diff --git a/models/event.js b/models/event.js index c3c501ebd..b63a0ec20 100644 --- a/models/event.js +++ b/models/event.js @@ -14,27 +14,6 @@ async function create(object, options = {}) { return results.rows[0]; } -// Currently it only update "metadata" column. -async function updateMetadata(eventId, object, options = {}) { - object = validateObject(object); - - const query = { - text: ` - UPDATE - events - SET - metadata = $1 - WHERE - id = $2 - RETURNING * - ;`, - values: [object.metadata, eventId], - }; - - const results = await database.query(query, options); - return results.rows[0]; -} - function validateObject(object) { const cleanObject = validator(object, { event: 'required', @@ -45,5 +24,4 @@ function validateObject(object) { export default Object.freeze({ create, - updateMetadata, }); diff --git a/pages/api/v1/contents/[username]/[slug]/index.public.js b/pages/api/v1/contents/[username]/[slug]/index.public.js index c3efde0d6..39aa92276 100644 --- a/pages/api/v1/contents/[username]/[slug]/index.public.js +++ b/pages/api/v1/contents/[username]/[slug]/index.public.js @@ -150,6 +150,9 @@ async function patchHandler(request, response) { type: contentToBeUpdated.parent_id ? 'update:content:text_child' : 'update:content:text_root', originatorUserId: request.context.user.id, originatorIp: request.context.clientIp, + metadata: { + id: contentToBeUpdated.id, + }, }, { transaction: transaction, @@ -157,22 +160,11 @@ async function patchHandler(request, response) { ); const updatedContent = await content.update(contentToBeUpdated.id, filteredBodyValues, { + oldContent: contentToBeUpdated, eventId: currentEvent.id, transaction: transaction, }); - await event.updateMetadata( - currentEvent.id, - { - metadata: { - id: updatedContent.id, - }, - }, - { - transaction: transaction, - }, - ); - await transaction.query('COMMIT'); await transaction.release(); diff --git a/pages/api/v1/contents/index.public.js b/pages/api/v1/contents/index.public.js index 22c5377cc..bfaced367 100644 --- a/pages/api/v1/contents/index.public.js +++ b/pages/api/v1/contents/index.public.js @@ -1,4 +1,5 @@ import nextConnect from 'next-connect'; +import { randomUUID as uuidV4 } from 'node:crypto'; import { ForbiddenError } from 'errors'; import database from 'infra/database.js'; @@ -133,6 +134,7 @@ async function postHandler(request, response) { } secureInputValues.owner_id = userTryingToCreate.id; + secureInputValues.id = uuidV4(); const transaction = await database.transaction(); @@ -144,6 +146,9 @@ async function postHandler(request, response) { type: secureInputValues.parent_id ? 'create:content:text_child' : 'create:content:text_root', originatorUserId: request.context.user.id, originatorIp: request.context.clientIp, + metadata: { + id: secureInputValues.id, + }, }, { transaction: transaction, @@ -155,18 +160,6 @@ async function postHandler(request, response) { transaction: transaction, }); - await event.updateMetadata( - currentEvent.id, - { - metadata: { - id: createdContent.id, - }, - }, - { - transaction: transaction, - }, - ); - await transaction.query('COMMIT'); await transaction.release(); diff --git a/pages/api/v1/users/[username]/index.public.js b/pages/api/v1/users/[username]/index.public.js index 5df431a11..68eed39bd 100644 --- a/pages/api/v1/users/[username]/index.public.js +++ b/pages/api/v1/users/[username]/index.public.js @@ -112,24 +112,15 @@ async function patchHandler(request, response) { try { await transaction.query('BEGIN'); - const currentEvent = await event.create( - { - type: 'update:user', - originatorUserId: request.context.user.id, - originatorIp: request.context.clientIp, - }, - { - transaction: transaction, - }, - ); - const updatedUser = await user.update(targetUsername, secureInputValues, { transaction: transaction, }); - await event.updateMetadata( - currentEvent.id, + await event.create( { + type: 'update:user', + originatorUserId: request.context.user.id, + originatorIp: request.context.clientIp, metadata: getEventMetadata(targetUser, updatedUser), }, { diff --git a/tests/orchestrator.js b/tests/orchestrator.js index c2cb950c4..e096d0903 100644 --- a/tests/orchestrator.js +++ b/tests/orchestrator.js @@ -177,13 +177,19 @@ async function findSessionByToken(token) { } async function createContent(contentObject) { + const contentId = contentObject?.id || randomUUID(); + const currentEvent = await event.create({ type: contentObject?.parent_id ? 'create:content:text_child' : 'create:content:text_root', originatorUserId: contentObject?.owner_id, + metadata: { + id: contentId, + }, }); const createdContent = await content.create( { + id: contentId, parent_id: contentObject?.parent_id || undefined, owner_id: contentObject?.owner_id || undefined, title: contentObject?.title || undefined, @@ -197,12 +203,6 @@ async function createContent(contentObject) { }, ); - await event.updateMetadata(currentEvent.id, { - metadata: { - id: createdContent.id, - }, - }); - return createdContent; }