import { MongoClient, type Collection } from 'mongodb' import type { Card, CardInput } from '../domain/card' import type { CardRepository } from '../domain/card-repository' export interface CardDocument { c_id: string question: string response: string theme: string tags: string[] createdAt: Date updatedAt: Date } const DOC_FIELDS = { _id: 0, c_id: 1, question: 1, response: 1, theme: 1, tags: 1, createdAt: 1, updatedAt: 1, } export class MongoCardRepository implements CardRepository { private readonly collection: Collection constructor(collection?: Collection) { if (collection) { this.collection = collection } else { const client = new MongoClient( process.env.MONGODB_URI ?? 'mongodb://flashcards:flashcards@localhost:27018/flashcards', ) this.collection = client .db(process.env.MONGODB_DATABASE ?? 'flashcards') .collection('cards') } } private mapDoc(doc: CardDocument): Card { return { id: doc.c_id, question: doc.question, response: doc.response, theme: doc.theme, tags: doc.tags, createdAt: doc.createdAt, updatedAt: doc.updatedAt, } } async findAll(): Promise { const docs = await this.collection.find({}, { projection: DOC_FIELDS }).toArray() return docs.map((doc) => this.mapDoc(doc)) } async findById(id: string): Promise { const doc = await this.collection.findOne({ c_id: id }, { projection: DOC_FIELDS }) return doc ? this.mapDoc(doc) : null } async create(input: CardInput): Promise { const doc: CardDocument = { c_id: crypto.randomUUID(), question: input.question, response: input.response, theme: input.theme, tags: input.tags ?? [], createdAt: new Date(), updatedAt: new Date(), } await this.collection.insertOne(doc) return this.mapDoc(doc) } async update(id: string, input: Partial): Promise { const set: Partial = { updatedAt: new Date() } if (input.question !== undefined) { set.question = input.question } if (input.response !== undefined) { set.response = input.response } if (input.theme !== undefined) { set.theme = input.theme } if (input.tags !== undefined) { set.tags = input.tags } const result = await this.collection.updateOne({ c_id: id }, { $set: set }) if (result.matchedCount === 0) { return null } return this.findById(id) } async delete(id: string): Promise { const result = await this.collection.deleteOne({ c_id: id }) return result.deletedCount > 0 } async findRandom(): Promise { const docs = await this.collection .aggregate([{ $sample: { size: 1 } }, { $project: DOC_FIELDS }]) .toArray() return docs[0] ? this.mapDoc(docs[0]) : null } async findRandomByTheme(theme: string): Promise { const docs = await this.collection .aggregate([ { $match: { theme } }, { $sample: { size: 1 } }, { $project: DOC_FIELDS }, ]) .toArray() return docs[0] ? this.mapDoc(docs[0]) : null } async findThemes(): Promise { const themes: string[] = await this.collection.distinct('theme') return themes.sort() } }