-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Notificações: Atualizações do Backend #1701
base: main
Are you sure you want to change the base?
Notificações: Atualizações do Backend #1701
Conversation
@mthmcalixto is attempting to deploy a commit to the TabNews Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mthmcalixto, obrigado pelo PR! 💪
Não cheguei a revisar tudo porque acredito que o que já comentei no código já vai lhe dar bastante coisa para adequar, e vai exigir mudanças inclusive nas partes que eu ainda não revisei.
Algo que não comentei no código, mas que acho importante repetir o que já tinha comentado em #1692, é que não precisamos de tantos endpoints. Provavelmente um /api/v1/notifications
com os métodos GET
, PATCH
e DELETE
deve ser suficiente.
type: 'varchar(254)', | ||
notNull: true, | ||
}, | ||
body_reply_line: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isso não precisa ser armazenado
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aprendendofelipe se não armazenar o body, precisa ter algo para armazenar o "title" do contéudo respondido.
Já tem o "type" então, mas precisa ter o username também e o title, o username de quem respondeu.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mthmcalixto acho que deve ser possível montar a mensagem da notificação a partir dos dados salvos na tabela.
Então, por exemplo, vamos supor que foi criado um comentário. Se armazenarmos o id
do evento, como mencionado em aqui, conseguimos acesso ao comentário através de events.metadata.id
. A partir disso, é possível conseguir o título da publicação e o nome de quem comentou.
WITH content_originator AS (
SELECT
path[1] as root_id,
owner_id
FROM events
INNER JOIN contents ON contents.id::text = events.metadata->>'id'
WHERE events.id = 'e4dfddb9-d16b-424d-b5b6-d512747b1ee3'
)
SELECT users.username, contents.title
FROM contents
INNER JOIN content_originator ON true
INNER JOIN users ON users.id = content_originator.owner_id
WHERE contents.id = content_originator.root_id
E a mensagem poderia ser montada: ${username} comentou na sua publicação ${title}
.
Como comentei em um PR meu (#1638 (comment)), não sei se teremos problemas de desempenho com essas queries buscando pelo metadata
numa tabela com tantos registros. Eu precisaria ler mais sobre isso e talvez seja necessário criar um índice. Se alguém tiver mais conhecimento sobre isso, pode me corrigir..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Rafatcb é justamente isso que queria evitar, passando apenas o ID iria precisa buscar em outro lugar o nome e o titulo, mas ao fazer igual o email, já retorna os dados prontos e salva na coluna username e o titulo da publicação.
type: 'varchar', | ||
notNull: true, | ||
}, | ||
content_link: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Melhor armazenar o originador da notificação de uma maneira mais flexível, que suporte outros tipos de notificações. Talvez armazenar o id do evento, mas é preciso pensar melhor sobre isso
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aprendendofelipe não consigo imaginar algo sem o "sender" de onde partiu aquela notificação.
podemos mudar "from_id" para "sender_id"
Podemos criar algo assim também:
Tipo de Notificação: Alerta
Evento: Novo Comentário
Mensagem: "Você recebeu um novo comentário em sua postagem. Verifique agora!"
Tipo de Notificação: Lembrete
Evento: Aniversário do Usuário
Mensagem: "Lembrete: Hoje é seu aniversário! Celebre seu dia especial conosco."
Aqui já iria ter o "sender_id", ele pode se opcional nesse caso.
Tipo de Notificação: Social
Evento: Mencionado em um Comentário
Mensagem: "@NomeDoUsuario mencionou você em um comentário. Confira agora!"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Podemos fazer com que sender_id
seja um array também, assim podemos colocar multiplos usuarios de uma vez só, algo com o @Rafatcb falou.
x usuários comentou em sua publicação...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tenho a impressão de que, para realizar o agrupamento de notificações, o ideal é agrupar na hora da consulta (precisaria pensar se isso é possível) ou no backend, e não salvar agrupado no banco de dados.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
models/notification.js
Outdated
@@ -2,10 +2,11 @@ import email from 'infra/email.js'; | |||
import webserver from 'infra/webserver.js'; | |||
import authorization from 'models/authorization.js'; | |||
import content from 'models/content.js'; | |||
import { NotificationWeb } from 'models/notifications'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Não é interessante ter um models/notifications/index.js
ao mesmo tempo que um models/notifications.js
.
Note que aqui está importando models/notifications
de dentro de models/notifications.js
, algo bem estranho.
Não sei se faz sentido essa pasta /web
e um arquivo manage.js
. Seria bom uma organização mais clara de models/notifications
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aprendendofelipe não existe um models/notifications.js
, existe um models/notification.js
que é responsavel pelo envio do email.
Minha ideia em notifications era separar notifications/web
, notifications/email
.
o from 'models/notifications'
é justamente a pasta de notifications
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aprendendofelipe já que estamos mexendo aqui, podemos mexer no email, mover ele pra dentro de models/notifications
models/authorization.js
Outdated
@@ -91,6 +91,7 @@ function filterInput(user, feature, input, target) { | |||
password: input.password, | |||
description: input.description, | |||
notifications: input.notifications, | |||
notifications_id: input.notifications_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Não entendi a necessidade de notifications_id
aqui e mais abaixo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aprendendofelipe era para permissão de saida, mais ainda não entendo muito o filterOutput.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Esse é o filterInput
e você está falando do filterOutput
, então fiquei confuso. Sobre a saída, em um endpoint exclusivo para obter notificações, acredito que você possa fazer algo parecido com read:user:self
:
tabnews.com.br/models/authorization.js
Lines 194 to 209 in 74ca378
if (feature === 'read:user:self') { | |
if (user.id && output.id && user.id === output.id) { | |
filteredOutputValues = { | |
id: output.id, | |
username: output.username, | |
email: output.email, | |
description: output.description, | |
notifications: output.notifications, | |
features: output.features, | |
tabcoins: output.tabcoins, | |
tabcash: output.tabcash, | |
created_at: output.created_at, | |
updated_at: output.updated_at, | |
}; | |
} | |
} |
Ou seja, você compara o id
do usuário notificado com o id
do usuário que fez a requisição.
Na prática, isso é só uma segunda barreira de segurança, já que você obtém o id
do usuário pelo context
.
Precisa pensar em um estrutura para /api/v1/notifications.
Talvez não deletar tudo, apenas colocar um status "delete" e um "delete_at", caso o usuário queira recuperar essas notificações. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obrigado por mais um PR!
Deixei apenas alguns comentários, já que ainda estamos discutindo sobre a estrutura da tabela, o que consequentemente mudará a implementação.
Houve uma discussão em algum lugar sobre o motivo de usar DELETE
(hard delete) na tabela de notificações? Me parece um ponto positivo mantermos o histórico, como mencionei em #738 (comment).
models/authorization.js
Outdated
@@ -91,6 +91,7 @@ function filterInput(user, feature, input, target) { | |||
password: input.password, | |||
description: input.description, | |||
notifications: input.notifications, | |||
notifications_id: input.notifications_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Esse é o filterInput
e você está falando do filterOutput
, então fiquei confuso. Sobre a saída, em um endpoint exclusivo para obter notificações, acredito que você possa fazer algo parecido com read:user:self
:
tabnews.com.br/models/authorization.js
Lines 194 to 209 in 74ca378
if (feature === 'read:user:self') { | |
if (user.id && output.id && user.id === output.id) { | |
filteredOutputValues = { | |
id: output.id, | |
username: output.username, | |
email: output.email, | |
description: output.description, | |
notifications: output.notifications, | |
features: output.features, | |
tabcoins: output.tabcoins, | |
tabcash: output.tabcash, | |
created_at: output.created_at, | |
updated_at: output.updated_at, | |
}; | |
} | |
} |
Ou seja, você compara o id
do usuário notificado com o id
do usuário que fez a requisição.
Na prática, isso é só uma segunda barreira de segurança, já que você obtém o id
do usuário pelo context
.
7399dd8
to
c6fad81
Compare
@Rafatcb verifique os novos códigos, refiz algumas coisas. Os endpoints:
Delete não deleta, apenas coloca status O que mais arrumei foi em Deixei sender_id caso mais pra frente a gente precise pegar o username do usuário que invocou aquela notificação, mas ele não é salvo no banco de dados. |
c6fad81
to
3d49fd3
Compare
- new endpoints - refactor models/notification.js - refactor models/notifications/web
3d49fd3
to
efcb6b0
Compare
O que falta para terminar as atualizações desse backend? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fiz mais comentários no código. Sugiro criar testes também para facilitar o entendimento de cada rota.
Nos comentários, questionei a utilidade de termos GET /api/v1/notifications/web/[id]
. Acredito que ela possa ser removida.
status: { | ||
type: 'varchar', | ||
default: 'unread', | ||
notNull: true, | ||
check: "status IN ('unread', 'read', 'draft')", | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considerando que as notificações poderão ser apagadas do banco de dados, eu tiraria o status de "soft delete". Como sobraria apenas o status unread
e read
, o que acham de substituir a coluna status
por uma coluna read
do tipo boolean
?
sendReplyEmailToParentUser, | ||
sendReplyWebToParentUser, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tenho a impressão que o que fará mais sentido para quem chama o modelo notification
será notificar sem saber a forma da notificação. Por exemplo, expotar uma função onContentReply
(ou algo parecido), e essa função envia a notificação por e-mail e web, ou nenhuma (a depender da configuração).
Acredito que esse é o melhor caminho porque assim centralizamos a responsabilidade de como determinada ação é notificada, inclusive facilitando quando futuramente termos configurações separadas para habilitar/desabilitar notificações por e-mail e web, ou quando termos mais tipos de notificação (o PR #1638 já cria alguns). O que vocês acham?
}); | ||
} | ||
|
||
if (notificationFound.status === 'read') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acredito que não precise retornar erro caso já esteja lida, apenas ignorar, retornando sucesso para o cliente.
Imagine uma conexão lenta onde o usuário marca a notificação como lida duas vezes, da forma como sugeri, o código do cliente não precisará tratar o erro nesse cenário, já que a ação desejada foi realizada (marcar a notificação como lida).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Rafatcb pensei em algo assim;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dê uma olhada na minha sugestão aqui. Se aceitarem, não precisará dessa verificação notificationFound.status === 'read'
.
async function deleteHandler(request, response) { | ||
const userTryingToDelete = request.context.user; | ||
|
||
const notificationsFound = await webNotifyQueries.findAllCountByUserId(userTryingToDelete.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tem algum motivo para realizar essa busca antes de buscar diretamente a notificação especificada pelo query.id
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Rafatcb é apenas para verificar se exister, não ir direto excluindo, pode acontecer que ele nao tenha, mas ele acesse o endpoint e force que alguma notificação seja excluida no banco de dados, antes disso acontecer, eu estou verificando se ela existe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pode acontecer que ele nao tenha, mas ele acesse o endpoint e force que alguma notificação seja excluida no banco de dados
Acho que isso não é um problema. Se não ter nada para ser atualizado ou removido, o update
ou delete
no banco de dados apenas não terá efeito.
.use(authentication.injectAnonymousOrUser) | ||
.use(controller.logRequest) | ||
.use(cacheControl.noCache) | ||
.get(authorization.canRequest('update:user'), getHandler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tenho a impressão que aqui deveria ser algo parecido como em GET /api/v1/user
:
.get(authorization.canRequest('read:session'), renewSessionIfNecessary, getHandler); |
const notificationsFound = await webNotifyQueries.findAllByUserId(userTryingToPatch.id); | ||
|
||
if (!notificationsFound || notificationsFound.length === 0) { | ||
throw new NotFoundError({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Não sei se vão concordar comigo, mas acho que uma experiência melhor nesse endpoint de PATCH
é atualizar tudo, sem retornar erro caso não existam notificações ou mesmo caso já estejam marcadas como lidas.
Mudanças realizadas
Adiciona novas páginas/api e lógicas relacionadas ao backend para o funcionamento do feat:notifications.
Tipo de mudança
[x] Nova funcionalidade
Checklist: