feat(repo): implémentation MySQL de CardRepository

This commit is contained in:
devcodetools committed 2026-09-21 17:46:45 +02:00
1 parent 2721326a07
commit ab24d329b9
1 file changed
+123
+123
View File
@@ -0,0 +1,123 @@
import { createPool, type Pool, type ResultSetHeader, type RowDataPacket } from 'mysql2/promise'
import type { Card, CardInput } from '../domain/card'
import type { CardRepository } from '../domain/card-repository'
interface CardRow extends RowDataPacket {
id: string
question: string
response: string
theme: string
tags: string | null
createdAt: Date
updatedAt: Date
}
const FIELDS = ['id', 'question', 'response', 'theme', 'tags', 'createdAt', 'updatedAt'] as const
export class MysqlCardRepository implements CardRepository {
private readonly pool: Pool
constructor(pool?: Pool) {
this.pool =
pool ??
createPool({
host: process.env.MYSQL_HOST ?? 'localhost',
port: Number(process.env.MYSQL_PORT ?? 13306),
user: process.env.MYSQL_USER ?? 'flashcards',
password: process.env.MYSQL_PASSWORD ?? 'flashcards',
database: process.env.MYSQL_DATABASE ?? 'flashcards',
})
}
private mapRow(row: CardRow): Card {
return {
id: row.id,
question: row.question,
response: row.response,
theme: row.theme,
tags: row.tags ? JSON.parse(row.tags) : [],
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt),
}
}
async findAll(): Promise<Card[]> {
const [rows] = await this.pool.query<CardRow[]>(`SELECT ${FIELDS.join(', ')} FROM cards`)
return rows.map((row) => this.mapRow(row))
}
async findById(id: string): Promise<Card | null> {
const [rows] = await this.pool.query<CardRow[]>(`SELECT ${FIELDS.join(', ')} FROM cards WHERE id = ?`, [id])
return rows[0] ? this.mapRow(rows[0]) : null
}
async create(input: CardInput): Promise<Card> {
const id = crypto.randomUUID()
const now = new Date()
await this.pool.query(
`INSERT INTO cards (${FIELDS.join(', ')}) VALUES (?, ?, ?, ?, ?, ?, ?)`,
[id, input.question, input.response, input.theme, JSON.stringify(input.tags ?? []), now, now],
)
return {
id,
question: input.question,
response: input.response,
theme: input.theme,
tags: input.tags ?? [],
createdAt: now,
updatedAt: now,
}
}
async update(id: string, input: Partial<CardInput>): Promise<Card | null> {
const sets: string[] = []
const values: unknown[] = []
if (input.question !== undefined) {
sets.push('question = ?')
values.push(input.question)
}
if (input.response !== undefined) {
sets.push('response = ?')
values.push(input.response)
}
if (input.theme !== undefined) {
sets.push('theme = ?')
values.push(input.theme)
}
if (input.tags !== undefined) {
sets.push('tags = ?')
values.push(JSON.stringify(input.tags))
}
if (sets.length === 0) {
return this.findById(id)
}
sets.push('updatedAt = ?')
values.push(new Date())
values.push(id)
await this.pool.query(`UPDATE cards SET ${sets.join(', ')} WHERE id = ?`, values)
return this.findById(id)
}
async delete(id: string): Promise<boolean> {
const [result] = await this.pool.query<ResultSetHeader>('DELETE FROM cards WHERE id = ?', [id])
return result.affectedRows > 0
}
async findRandom(): Promise<Card | null> {
const [rows] = await this.pool.query<CardRow[]>(`SELECT ${FIELDS.join(', ')} FROM cards ORDER BY RAND() LIMIT 1`)
return rows[0] ? this.mapRow(rows[0]) : null
}
async findRandomByTheme(theme: string): Promise<Card | null> {
const [rows] = await this.pool.query<CardRow[]>(
`SELECT ${FIELDS.join(', ')} FROM cards WHERE theme = ? ORDER BY RAND() LIMIT 1`,
[theme],
)
return rows[0] ? this.mapRow(rows[0]) : null
}
async findThemes(): Promise<string[]> {
const [rows] = await this.pool.query<RowDataPacket[]>('SELECT DISTINCT theme FROM cards ORDER BY theme')
return rows.map((row) => row.theme as string)
}
}