diego3g / rsxp-2023

Repositório contendo o código do app do RS/XP 2023
MIT License
443 stars 85 forks source link

[0017] Buscar ingresso vinculado #86

Closed trolliama closed 1 year ago

trolliama commented 1 year ago

Eu sei que ainda falta o esquema da comunicação que será feita entre o aplicativo e o backend mas tomei a liberdade de ir testando algumas coisas. Gostaria de trabalhar nesta feature quando o esquema do contrato estiver completo :smile:

Fiz um esquema de como mais ou menos eu imagino que seria feito essa feature:

// app.controller.ts

import { Controller, Get, Param } from '@nestjs/common'
import { PrismaService } from './database/prisma.service'
import { GetTicketDto } from './agreements/retrieveTicket'

@Controller()
export class AppController {
  constructor(private prisma: PrismaService) {}

  ...

  @Get('/users/:userId/tickets/:ticketNumber')
  async getTicket(@Param() params: GetTicketDto) {
    const { userId, ticketNumber } = params

    const ticketUser = await this.prisma.ticketToUser.findUnique({
      where: {
        ticketNumber,
      },
    })

    if (!ticketUser) {
      // TODO: throw error (404 status code?)
      return { data: null }
    }

    if (ticketUser.userId !== userId) {
      // TODO: throw error (403 or 401 status code?)
      return { data: { message: 'unauthorized' } }
    }

    return { data: ticketUser }
  }
}

// agreements/retrieveTickets.ts

import * as z from 'zod'

export const getTicketDto = z.object({
  userId: z.string(),
  ticketNumber: z.string(),
})

export type GetTicketDto = z.infer<typeof getTicketDto>

//schema.prisma

model TicketToUser {
  user         User     @relation(fields: [userId], references: [id])
  userId       String   @unique
  ticketNumber String   @unique
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@id([userId, ticketNumber])
}
eletroswing commented 1 year ago

O que você acha de removermos a responsabilidade de pesquisa ao banco do controller e passarmos para um service, assim poderemos reultilizar em outras partes do código, caso necessário, bem como criar um controller só para os tickets. O Ticket tem mais argumentos, retornados do sympla, como o nome do usuário, email, o texto do código qr, temos que conferir o que vale a pena ser salvo no banco. E uma das coisas que mais me preocupam, é que se um usuário malicioso chegar até essa rota, ele pode registrar todos os ingressos com seu user id, por não termos muitos filtros por agora, como autenticação, segue a discussão 84 onde o ponto é levantado!

diego3g commented 1 year ago

@trolliama Podemos usar um findUnique direto pelo userId já que cada usuário só terá um ingresso no máximo evitando a necessidade do front-end enviar o número do ingresso.

Sobre o service, podemos separar pra tirar do controller sim, mas acho que, como falei, por enquanto, quando mais simples melhor.

trolliama commented 1 year ago

@diego3g Nesse caso o back apenas retornaria o número do ingresso do usuário correto? Imagino que, após isso, a partir dele será criado o qr code e este será validado a partir da api do sympla?

eletroswing commented 1 year ago

@diego3g @trolliama essa questão da segurança, posso tentar fazer um auth guard integrado ao clark (referenciado na live de Setup) e já fazer um pull para deixar essa feature dos tickets mais segura. O que acham?

e:

@diego3g Nesse caso o back apenas retornaria o número do ingresso do usuário correto? Imagino que, após isso, a partir dele será criado o qr code e este será validado a partir da api do sympla?

o retorno do ingresso pode ser só na rota get, para pegar o ingresso, e o envio poderia ser só na rota post, que cria o vinculo do ingresso. Para as rotas delete e get poderia ser enviado só o user id e no caso de integração de auth guard, o header de autorização!

edit 1: Fiz o guarda, e a fim de demonstrar o que quero dizer, o mesmo está presente na solicitação de Pull Request 94.

trolliama commented 1 year ago

@eletroswing Acho uma ótima ideia e vi que o pull request foi aceito. Quanto a questão de apenas mandar o id do usuario para pegar e deletar o ticket, a ideia é exatamente essa eu que acabei não entendendo corretamente mas já irei ajeitar

trolliama commented 1 year ago

@diego3g e @eletroswing Fiz as mudanças usando o auth guard. Por favor deem uma checada e assim que for possível eu faço os testes necessários e abro uma PR.

// app.controller.ts

import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common'
import { PrismaService } from './database/prisma.service'
import { ClerkAuthGuard } from './clerk/clerk.guard'
import { RequestWithUser } from './clerk/requestInterface.dto'
import { GetTicketDto } from './agreements/retrieveTicket'

@Controller()
export class AppController {
  constructor(private prisma: PrismaService) {}

  @Get()
  async getUsers() {
    const users = await this.prisma.user.findMany()

    return {
      data: users,
    }
  }

  @Get('/users/:userId/ticket')
  @UseGuards(ClerkAuthGuard)
  async getTicket(@Req() req: RequestWithUser, @Param() params: GetTicketDto) {
    const { userId } = params

    if (req.user.id !== userId) {
      // TODO: throw error (403 or 401 status code?)
      return { data: { message: 'unauthorized' } }
    }

    const ticketUser = await this.prisma.ticketToUser.findUnique({
      where: {
        userId,
      },
    })

    if (!ticketUser) {
      // TODO: throw error (404 status code?)
      return { data: null }
    }

    return { data: ticketUser }
  }
}

// agreements/retrieveTickets.ts

import * as z from 'zod'

export const getTicketDto = z.object({
  userId: z.string(),
})

export type GetTicketDto = z.infer<typeof getTicketDto>
eletroswing commented 1 year ago

o Id que vc está usando ai é o client, o id do user(presente no front end) vem nas seções do usuario, da uma olhada:

{
        "id": "client_2O7jNpqHMuzFQvpjTHZnsXX8x0m",
        "sessionIds": [
            "sess_2O7jNq3zziuyTrkg8myZ2pLTDwd"
        ],
        "sessions": [
            {
                "id": "sess_2O7jNq3zziuyTrkg8myZ2pLTDwd",
                "clientId": "client_2O7jNpqHMuzFQvpjTHZnsXX8x0m",
                "userId": "user_2O7jK6GKRv3exxSxts0RG05vaPd",
                "status": "expired",
                "lastActiveAt": 1680918943789,
                "expireAt": 1680920276065,
                "abandonAt": 1683510476063,
                "createdAt": 1680918476065,
                "updatedAt": 1680918943789
            }
        ],
        "signInId": null,
        "signUpId": null,
        "lastActiveSessionId": null,
        "createdAt": 1680918476014,
        "updatedAt": 1680918476086
    }

@trolliama me adiciona no discord pra gente codar isso junto, ai já fazemos o crud de pegar, vincular e deletar o ticekt e o service do ticket, ai temos uma evolução juntos, o que acha? tag: Fountai#9077

ou me manda o link da sua fork, pra trabalharmos juntos na implementação!

trolliama commented 1 year ago

Massa! Já enviei o pedido

trolliama commented 1 year ago

Dei uma atualizada junto com o @eletroswing no controller da rota!

Para resolver a issue mais facilmente, foi pensado o modelo completo de crud (presente no seguinte fork.). O código final ficou assim(isolando a parte de buscar o ingresso):

//schema.prisma

model Tickets {
  userId       String @unique
  ticketNumber String @unique
  @@id([userId, ticketNumber])
}

//src/tickets/tickets.service

import { Injectable } from '@nestjs/common'
import { PrismaService } from 'src/database/prisma.service'

@Injectable()
export class TicketsService {
  constructor(private prisma: PrismaService) {}

  async getTicketByUserId(userId: string) {
    return await this.prisma.tickets.findUnique({ where: { userId } })
  }
}

//app.controller

  @Get('/users/ticket')
  @UseGuards(ClerkAuthGuard)
  async getTicketByUserId(@Req() req: RequestWithUser, @Res() res: Response) {
    const userId = req.user.sessions[0].userId
    const ticketInDb = await this.ticketsService.getTicketByUserId(userId)

    if (!ticketInDb) {
      return res
        .status(404)
        .json({ data: null, error: 'Ticket não encontrado' })
    }

    if (ticketInDb.userId !== userId) {
      res
        .status(403)
        .json({ data: null, error: 'O conteúdo pertence a outro usuário.' })
    }
    return {
      data: ticketInDb,
    }
  }
diego3g commented 1 year ago

Fala @trolliama, implementei essa parte de link nos últimos commits que fiz e ficou bem legal caso queira dar uma olhada! Obrigado! <3

Agora na v2 vai ter muito mais coisa pra implementar!