From d95d5d8e205570ef7f2e22f8bb0f792f1e5d5dd2 Mon Sep 17 00:00:00 2001 From: andres Date: Sat, 20 Jan 2024 16:07:32 +0100 Subject: [PATCH] fix: cardsCount --- package.json | 2 +- pnpm-lock.yaml | 14 +- prisma/schema.prisma | 26 +-- .../cards/infrastructure/cards.repository.ts | 62 ++--- src/modules/decks/entities/deck.entity.ts | 1 - .../decks/infrastructure/decks.repository.ts | 213 +++++++++++++----- 6 files changed, 193 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index 3c33609..fed9699 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@nestjs/schedule": "^4.0.0", "@nestjs/serve-static": "^4.0.0", "@nestjs/swagger": "^7.1.16", - "@prisma/client": "4.14.0", + "@prisma/client": "4.16.0", "@types/passport-local": "^1.0.38", "aws-sdk": "^2.1499.0", "bcrypt": "5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92134c2..5aee81b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ dependencies: specifier: ^7.1.16 version: 7.1.16(@nestjs/common@10.2.9)(@nestjs/core@10.2.9)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) '@prisma/client': - specifier: 4.14.0 - version: 4.14.0(prisma@5.6.0) + specifier: 4.16.0 + version: 4.16.0(prisma@5.6.0) '@types/passport-local': specifier: ^1.0.38 version: 1.0.38 @@ -2041,8 +2041,8 @@ packages: tslib: 2.6.2 dev: true - /@prisma/client@4.14.0(prisma@5.6.0): - resolution: {integrity: sha512-MK/XaA2sFdfaOa7I9MjNKz6dxeIEdeZlnpNRoF2w3JuRLlFJLkpp6cD3yaqw2nUUhbrn3Iqe3ZpVV+VuGGil7Q==} + /@prisma/client@4.16.0(prisma@5.6.0): + resolution: {integrity: sha512-CBD+5IdZPiavhLkQokvsz1uz4r9ppixaqY/ajybWs4WXNnsDVMBKEqN3BiPzpSo79jiy22VKj/67pqt4VwIg9w==} engines: {node: '>=14.17'} requiresBuild: true peerDependencies: @@ -2051,12 +2051,12 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c + '@prisma/engines-version': 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c prisma: 5.6.0 dev: false - /@prisma/engines-version@4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c: - resolution: {integrity: sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw==} + /@prisma/engines-version@4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c: + resolution: {integrity: sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==} dev: false /@prisma/engines@5.6.0: diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6347045..326eb44 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -103,20 +103,18 @@ model card { } model deck { - id String @id @default(cuid()) - userId String - name String - isPrivate Boolean @default(false) - shots Int @default(0) - cover String? @db.VarChar(500) - isDeleted Boolean? - isBlocked Boolean? - created DateTime @default(now()) - updated DateTime @updatedAt - cardsCount Int @default(0) - author user @relation(fields: [userId], references: [id], onDelete: Cascade) - card card[] - grades grade[] + id String @id @default(cuid()) + userId String + name String + isPrivate Boolean @default(false) + cover String? @db.VarChar(500) + isDeleted Boolean? + isBlocked Boolean? + created DateTime @default(now()) + updated DateTime @updatedAt + author user @relation(fields: [userId], references: [id], onDelete: Cascade) + card card[] + grades grade[] @@index([userId]) } diff --git a/src/modules/cards/infrastructure/cards.repository.ts b/src/modules/cards/infrastructure/cards.repository.ts index e21c199..6f34ec8 100644 --- a/src/modules/cards/infrastructure/cards.repository.ts +++ b/src/modules/cards/infrastructure/cards.repository.ts @@ -18,36 +18,21 @@ export class CardsRepository { async createCard(deckId: string, userId: string, card: CreateCardDto) { try { - return await this.prisma.$transaction(async tx => { - const created = await tx.card.create({ - data: { - author: { - connect: { - id: userId, - }, - }, - decks: { - connect: { - id: deckId, - }, - }, - - ...card, - }, - }) - - await tx.deck.update({ - where: { - id: deckId, - }, - data: { - cardsCount: { - increment: 1, + return await this.prisma.card.create({ + data: { + author: { + connect: { + id: userId, + }, + }, + decks: { + connect: { + id: deckId, }, }, - }) - return created + ...card, + }, }) } catch (e) { this.logger.error(e?.message) @@ -186,25 +171,10 @@ export class CardsRepository { public async deleteCardById(id: string) { try { - return await this.prisma.$transaction(async tx => { - const deleted = await tx.card.delete({ - where: { - id, - }, - }) - - await tx.deck.update({ - where: { - id: deleted.deckId, - }, - data: { - cardsCount: { - decrement: 1, - }, - }, - }) - - return deleted + return await this.prisma.card.delete({ + where: { + id, + }, }) } catch (e) { this.logger.error(e?.message) diff --git a/src/modules/decks/entities/deck.entity.ts b/src/modules/decks/entities/deck.entity.ts index ab02c2c..1a1c0f1 100644 --- a/src/modules/decks/entities/deck.entity.ts +++ b/src/modules/decks/entities/deck.entity.ts @@ -5,7 +5,6 @@ export class Deck { userId: string name: string isPrivate: boolean - shots: number cover: string | null created: Date updated: Date diff --git a/src/modules/decks/infrastructure/decks.repository.ts b/src/modules/decks/infrastructure/decks.repository.ts index 1567df3..6396d07 100644 --- a/src/modules/decks/infrastructure/decks.repository.ts +++ b/src/modules/decks/infrastructure/decks.repository.ts @@ -1,7 +1,5 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common' -import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-order-by-object' -import { Pagination } from '../../../infrastructure/common/pagination/pagination.service' import { PrismaService } from '../../../prisma.service' import { GetAllDecksDto } from '../dto' import { Deck, PaginatedDecks } from '../entities/deck.entity' @@ -24,7 +22,7 @@ export class DecksRepository { isPrivate?: boolean }): Promise { try { - return await this.prisma.deck.create({ + const result = await this.prisma.deck.create({ data: { author: { connect: { @@ -37,6 +35,11 @@ export class DecksRepository { isPrivate, }, include: { + _count: { + select: { + card: true, + }, + }, author: { select: { id: true, @@ -45,6 +48,8 @@ export class DecksRepository { }, }, }) + + return { ...result, cardsCount: result._count.card } } catch (e) { this.logger.error(e?.message) throw new InternalServerErrorException(e?.message) @@ -62,62 +67,133 @@ export class DecksRepository { orderBy, }: GetAllDecksDto): Promise { if (!orderBy || orderBy === 'null') { - orderBy = 'updated-desc' + orderBy = 'updated-desc' // Adjust this based on your actual ordering requirements + } + let orderField = 'd.updated' // default order field + let orderDirection = 'DESC' // default order direction + + if (orderBy) { + const orderByParts = orderBy.split('-') + + if (orderByParts.length === 2) { + const field = orderByParts[0] + const direction = orderByParts[1].toUpperCase() + + // Validate the field and direction + if ( + ['cardsCount', 'updated', 'name', 'author.name', 'created'].includes(field) && + ['ASC', 'DESC'].includes(direction) + ) { + if (field === 'cardsCount') { + orderField = 'cardsCount' + } else if (field === 'author.name') { + orderField = 'a.name' + } else { + orderField = `d.${field}` + } + + orderDirection = direction + } + } } try { - const where = { - cardsCount: { - gte: minCardsCount ? Number(minCardsCount) : undefined, - lte: maxCardsCount ? Number(maxCardsCount) : undefined, - }, - name: { - contains: name, - }, - author: { - id: authorId || undefined, - }, - OR: [ - { - AND: [ - { - isPrivate: true, - }, - { - userId: userId, - }, - ], - }, - { - isPrivate: false, - }, - ], - } + // Prepare the where clause conditions + const conditions = [] - const [count, items, max] = await this.prisma.$transaction([ - this.prisma.deck.count({ - where, - }), - this.prisma.deck.findMany({ - where, - orderBy: createPrismaOrderBy(orderBy), - include: { - author: { - select: { - id: true, - name: true, - }, - }, - }, - skip: (currentPage - 1) * itemsPerPage, - take: itemsPerPage, - }), - this.prisma - .$queryRaw`SELECT MAX(card_count) as maxCardsCount FROM (SELECT COUNT(*) as card_count FROM card GROUP BY deckId) AS card_counts;`, - ]) + if (name) conditions.push(`d.name LIKE CONCAT('%', ?, '%')`) + if (authorId) conditions.push(`d.userId = ?`) + if (userId) conditions.push(`(d.isPrivate = FALSE OR (d.isPrivate = TRUE AND d.userId = ?))`) + // Prepare the having clause for card count range + const havingConditions = [] + + if (minCardsCount != null) havingConditions.push(`COUNT(c.id) >= ?`) + if (maxCardsCount != null) havingConditions.push(`COUNT(c.id) <= ?`) + + // Construct the raw SQL query for fetching decks + const query = ` + SELECT + d.*, + COUNT(c.id) AS cardsCount, + a.id AS authorId, + a.name AS authorName + FROM deck AS d + LEFT JOIN card AS c ON d.id = c.deckId + LEFT JOIN user AS a ON d.userId = a.id + ${conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''} + GROUP BY d.id + ${havingConditions.length ? `HAVING ${havingConditions.join(' AND ')}` : ''} + ORDER BY ${orderField} ${orderDirection} + LIMIT ? OFFSET ?; + ` + + // Parameters for fetching decks + const deckQueryParams = [ + ...(name ? [name] : []), + ...(authorId ? [authorId] : []), + ...(userId ? [userId] : []), + ...(minCardsCount != null ? [minCardsCount] : []), + ...(maxCardsCount != null ? [maxCardsCount] : []), + itemsPerPage, + (currentPage - 1) * itemsPerPage, + ] + + // Execute the raw SQL query for fetching decks + const decks = await this.prisma.$queryRawUnsafe< + Array< + Deck & { + authorId: string + authorName: string + } + > + >(query, ...deckQueryParams) + // Construct the raw SQL query for total count + const countQuery = ` + SELECT COUNT(DISTINCT d.id) AS total + FROM deck AS d + LEFT JOIN card AS c ON d.id = c.deckId + ${conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''} + ${havingConditions.length ? `GROUP BY d.id HAVING ${havingConditions.join(' AND ')}` : ''}; + ` + + // Parameters for total count query + const countQueryParams = [ + ...(name ? [name] : []), + ...(authorId ? [authorId] : []), + ...(userId ? [userId] : []), + ...(minCardsCount != null ? [minCardsCount] : []), + ...(maxCardsCount != null ? [maxCardsCount] : []), + ] + + // Execute the raw SQL query for total count + const totalResult = await this.prisma.$queryRawUnsafe(countQuery, ...countQueryParams) + + const total = totalResult.length + const modifiedDecks = decks.map(deck => { + const cardsCount = deck.cardsCount + + return { + ...deck, + cardsCount: typeof cardsCount === 'bigint' ? Number(cardsCount) : cardsCount, + author: { + id: deck.authorId, + name: deck.authorName, + }, + } + }) + const max = await this.prisma + .$queryRaw`SELECT MAX(card_count) as maxCardsCount FROM (SELECT COUNT(*) as card_count FROM card GROUP BY deckId) AS card_counts;` + + // Return the result with pagination data return { + items: modifiedDecks, + pagination: { + totalItems: total, + currentPage, + itemsPerPage, + totalPages: Math.ceil(total / itemsPerPage), + }, maxCardsCount: Number(max[0].maxCardsCount), - ...Pagination.transformPaginationData([count, items], { currentPage, itemsPerPage }), } } catch (e) { this.logger.error(e?.message) @@ -127,11 +203,20 @@ export class DecksRepository { public async findDeckById(id: string): Promise { try { - return await this.prisma.deck.findUnique({ + const result = await this.prisma.deck.findUnique({ where: { id, }, + include: { + _count: { + select: { + card: true, + }, + }, + }, }) + + return { ...result, cardsCount: result._count.card } } catch (e) { this.logger.error(e?.message) throw new InternalServerErrorException(e?.message) @@ -146,11 +231,20 @@ export class DecksRepository { }, }) - return await this.prisma.deck.findUnique({ + const result = await this.prisma.deck.findUnique({ + include: { + _count: { + select: { + card: true, + }, + }, + }, where: { id: card.deckId, }, }) + + return { ...result, cardsCount: result._count.card } } catch (e) { this.logger.error(e?.message) throw new InternalServerErrorException(e?.message) @@ -175,12 +269,17 @@ export class DecksRepository { data: { name?: string; cover?: string; isPrivate?: boolean } ): Promise { try { - return await this.prisma.deck.update({ + const result = await this.prisma.deck.update({ where: { id, }, data, include: { + _count: { + select: { + card: true, + }, + }, author: { select: { id: true, @@ -189,6 +288,8 @@ export class DecksRepository { }, }, }) + + return { ...result, cardsCount: result._count.card } } catch (e) { this.logger.error(e?.message) throw new InternalServerErrorException(e?.message)