mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-16 20:59:26 +00:00
fix: cardsCount
This commit is contained in:
@@ -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
14
pnpm-lock.yaml
generated
@@ -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:
|
||||||
|
|||||||
@@ -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])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user