fix: cardsCount

This commit is contained in:
2024-01-20 16:07:32 +01:00
parent e9e30fe435
commit d95d5d8e20
6 changed files with 193 additions and 125 deletions

View File

@@ -39,7 +39,7 @@
"@nestjs/schedule": "^4.0.0", "@nestjs/schedule": "^4.0.0",
"@nestjs/serve-static": "^4.0.0", "@nestjs/serve-static": "^4.0.0",
"@nestjs/swagger": "^7.1.16", "@nestjs/swagger": "^7.1.16",
"@prisma/client": "4.14.0", "@prisma/client": "4.16.0",
"@types/passport-local": "^1.0.38", "@types/passport-local": "^1.0.38",
"aws-sdk": "^2.1499.0", "aws-sdk": "^2.1499.0",
"bcrypt": "5.1.0", "bcrypt": "5.1.0",

14
pnpm-lock.yaml generated
View File

@@ -51,8 +51,8 @@ dependencies:
specifier: ^7.1.16 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) 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': '@prisma/client':
specifier: 4.14.0 specifier: 4.16.0
version: 4.14.0(prisma@5.6.0) version: 4.16.0(prisma@5.6.0)
'@types/passport-local': '@types/passport-local':
specifier: ^1.0.38 specifier: ^1.0.38
version: 1.0.38 version: 1.0.38
@@ -2041,8 +2041,8 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: true dev: true
/@prisma/client@4.14.0(prisma@5.6.0): /@prisma/client@4.16.0(prisma@5.6.0):
resolution: {integrity: sha512-MK/XaA2sFdfaOa7I9MjNKz6dxeIEdeZlnpNRoF2w3JuRLlFJLkpp6cD3yaqw2nUUhbrn3Iqe3ZpVV+VuGGil7Q==} resolution: {integrity: sha512-CBD+5IdZPiavhLkQokvsz1uz4r9ppixaqY/ajybWs4WXNnsDVMBKEqN3BiPzpSo79jiy22VKj/67pqt4VwIg9w==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
requiresBuild: true requiresBuild: true
peerDependencies: peerDependencies:
@@ -2051,12 +2051,12 @@ packages:
prisma: prisma:
optional: true optional: true
dependencies: dependencies:
'@prisma/engines-version': 4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c '@prisma/engines-version': 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
prisma: 5.6.0 prisma: 5.6.0
dev: false dev: false
/@prisma/engines-version@4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c: /@prisma/engines-version@4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c:
resolution: {integrity: sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw==} resolution: {integrity: sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==}
dev: false dev: false
/@prisma/engines@5.6.0: /@prisma/engines@5.6.0:

View File

@@ -103,20 +103,18 @@ model card {
} }
model deck { model deck {
id String @id @default(cuid()) id String @id @default(cuid())
userId String userId String
name String name String
isPrivate Boolean @default(false) isPrivate Boolean @default(false)
shots Int @default(0) cover String? @db.VarChar(500)
cover String? @db.VarChar(500) isDeleted Boolean?
isDeleted Boolean? isBlocked Boolean?
isBlocked Boolean? created DateTime @default(now())
created DateTime @default(now()) updated DateTime @updatedAt
updated DateTime @updatedAt author user @relation(fields: [userId], references: [id], onDelete: Cascade)
cardsCount Int @default(0) card card[]
author user @relation(fields: [userId], references: [id], onDelete: Cascade) grades grade[]
card card[]
grades grade[]
@@index([userId]) @@index([userId])
} }

View File

@@ -18,36 +18,21 @@ export class CardsRepository {
async createCard(deckId: string, userId: string, card: CreateCardDto) { async createCard(deckId: string, userId: string, card: CreateCardDto) {
try { try {
return await this.prisma.$transaction(async tx => { return await this.prisma.card.create({
const created = await tx.card.create({ data: {
data: { author: {
author: { connect: {
connect: { id: userId,
id: userId, },
}, },
}, decks: {
decks: { connect: {
connect: { id: deckId,
id: deckId,
},
},
...card,
},
})
await tx.deck.update({
where: {
id: deckId,
},
data: {
cardsCount: {
increment: 1,
}, },
}, },
})
return created ...card,
},
}) })
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
@@ -186,25 +171,10 @@ export class CardsRepository {
public async deleteCardById(id: string) { public async deleteCardById(id: string) {
try { try {
return await this.prisma.$transaction(async tx => { return await this.prisma.card.delete({
const deleted = await tx.card.delete({ where: {
where: { id,
id, },
},
})
await tx.deck.update({
where: {
id: deleted.deckId,
},
data: {
cardsCount: {
decrement: 1,
},
},
})
return deleted
}) })
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)

View File

@@ -5,7 +5,6 @@ export class Deck {
userId: string userId: string
name: string name: string
isPrivate: boolean isPrivate: boolean
shots: number
cover: string | null cover: string | null
created: Date created: Date
updated: Date updated: Date

View File

@@ -1,7 +1,5 @@
import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common' 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 { PrismaService } from '../../../prisma.service'
import { GetAllDecksDto } from '../dto' import { GetAllDecksDto } from '../dto'
import { Deck, PaginatedDecks } from '../entities/deck.entity' import { Deck, PaginatedDecks } from '../entities/deck.entity'
@@ -24,7 +22,7 @@ export class DecksRepository {
isPrivate?: boolean isPrivate?: boolean
}): Promise<Deck> { }): Promise<Deck> {
try { try {
return await this.prisma.deck.create({ const result = await this.prisma.deck.create({
data: { data: {
author: { author: {
connect: { connect: {
@@ -37,6 +35,11 @@ export class DecksRepository {
isPrivate, isPrivate,
}, },
include: { include: {
_count: {
select: {
card: true,
},
},
author: { author: {
select: { select: {
id: true, id: true,
@@ -45,6 +48,8 @@ export class DecksRepository {
}, },
}, },
}) })
return { ...result, cardsCount: result._count.card }
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
throw new InternalServerErrorException(e?.message) throw new InternalServerErrorException(e?.message)
@@ -62,62 +67,133 @@ export class DecksRepository {
orderBy, orderBy,
}: GetAllDecksDto): Promise<PaginatedDecks> { }: GetAllDecksDto): Promise<PaginatedDecks> {
if (!orderBy || orderBy === 'null') { 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 { try {
const where = { // Prepare the where clause conditions
cardsCount: { const conditions = []
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,
},
],
}
const [count, items, max] = await this.prisma.$transaction([ if (name) conditions.push(`d.name LIKE CONCAT('%', ?, '%')`)
this.prisma.deck.count({ if (authorId) conditions.push(`d.userId = ?`)
where, if (userId) conditions.push(`(d.isPrivate = FALSE OR (d.isPrivate = TRUE AND d.userId = ?))`)
}),
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;`,
])
// 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<any[]>(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 { return {
items: modifiedDecks,
pagination: {
totalItems: total,
currentPage,
itemsPerPage,
totalPages: Math.ceil(total / itemsPerPage),
},
maxCardsCount: Number(max[0].maxCardsCount), maxCardsCount: Number(max[0].maxCardsCount),
...Pagination.transformPaginationData([count, items], { currentPage, itemsPerPage }),
} }
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
@@ -127,11 +203,20 @@ export class DecksRepository {
public async findDeckById(id: string): Promise<Deck> { public async findDeckById(id: string): Promise<Deck> {
try { try {
return await this.prisma.deck.findUnique({ const result = await this.prisma.deck.findUnique({
where: { where: {
id, id,
}, },
include: {
_count: {
select: {
card: true,
},
},
},
}) })
return { ...result, cardsCount: result._count.card }
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
throw new InternalServerErrorException(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: { where: {
id: card.deckId, id: card.deckId,
}, },
}) })
return { ...result, cardsCount: result._count.card }
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
throw new InternalServerErrorException(e?.message) throw new InternalServerErrorException(e?.message)
@@ -175,12 +269,17 @@ export class DecksRepository {
data: { name?: string; cover?: string; isPrivate?: boolean } data: { name?: string; cover?: string; isPrivate?: boolean }
): Promise<Deck> { ): Promise<Deck> {
try { try {
return await this.prisma.deck.update({ const result = await this.prisma.deck.update({
where: { where: {
id, id,
}, },
data, data,
include: { include: {
_count: {
select: {
card: true,
},
},
author: { author: {
select: { select: {
id: true, id: true,
@@ -189,6 +288,8 @@ export class DecksRepository {
}, },
}, },
}) })
return { ...result, cardsCount: result._count.card }
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
throw new InternalServerErrorException(e?.message) throw new InternalServerErrorException(e?.message)