- Introduced a new Note model in the Prisma schema to support note-taking functionality. - Updated the HeaderNavigation component to include a link to the new Notes page. - Implemented keyboard shortcuts for note actions, enhancing user experience and productivity. - Added dependencies for markdown rendering and formatting tools to support note content.
273 lines
5.7 KiB
TypeScript
273 lines
5.7 KiB
TypeScript
import { prisma } from '@/services/core/database';
|
|
|
|
export interface Note {
|
|
id: string;
|
|
title: string;
|
|
content: string;
|
|
userId: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
tags?: string[];
|
|
}
|
|
|
|
export interface CreateNoteData {
|
|
title: string;
|
|
content: string;
|
|
userId: string;
|
|
tags?: string[];
|
|
}
|
|
|
|
export interface UpdateNoteData {
|
|
title?: string;
|
|
content?: string;
|
|
tags?: string[];
|
|
}
|
|
|
|
/**
|
|
* Service pour la gestion des notes markdown
|
|
*/
|
|
export class NotesService {
|
|
/**
|
|
* Récupère toutes les notes d'un utilisateur
|
|
*/
|
|
async getNotes(userId: string): Promise<Note[]> {
|
|
const notes = await prisma.note.findMany({
|
|
where: { userId },
|
|
include: {
|
|
noteTags: {
|
|
include: {
|
|
tag: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { updatedAt: 'desc' },
|
|
});
|
|
|
|
return notes.map((note) => ({
|
|
...note,
|
|
tags: note.noteTags.map((nt) => nt.tag.name),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Récupère une note par son ID
|
|
*/
|
|
async getNoteById(noteId: string, userId: string): Promise<Note | null> {
|
|
const note = await prisma.note.findFirst({
|
|
where: {
|
|
id: noteId,
|
|
userId,
|
|
},
|
|
include: {
|
|
noteTags: {
|
|
include: {
|
|
tag: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!note) return null;
|
|
|
|
return {
|
|
...note,
|
|
tags: note.noteTags.map((nt) => nt.tag.name),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Crée une nouvelle note
|
|
*/
|
|
async createNote(data: CreateNoteData): Promise<Note> {
|
|
const note = await prisma.note.create({
|
|
data: {
|
|
title: data.title,
|
|
content: data.content,
|
|
userId: data.userId,
|
|
noteTags: data.tags
|
|
? {
|
|
create: data.tags.map((tagName) => ({
|
|
tag: {
|
|
connectOrCreate: {
|
|
where: { name: tagName },
|
|
create: { name: tagName },
|
|
},
|
|
},
|
|
})),
|
|
}
|
|
: undefined,
|
|
},
|
|
include: {
|
|
noteTags: {
|
|
include: {
|
|
tag: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
...note,
|
|
tags: note.noteTags.map((nt) => nt.tag.name),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Met à jour une note existante
|
|
*/
|
|
async updateNote(
|
|
noteId: string,
|
|
userId: string,
|
|
data: UpdateNoteData
|
|
): Promise<Note> {
|
|
// Vérifier que la note appartient à l'utilisateur
|
|
const existingNote = await prisma.note.findFirst({
|
|
where: {
|
|
id: noteId,
|
|
userId,
|
|
},
|
|
});
|
|
|
|
if (!existingNote) {
|
|
throw new Error('Note not found or access denied');
|
|
}
|
|
|
|
// Préparer les données de mise à jour
|
|
const updateData: {
|
|
updatedAt: Date;
|
|
title?: string;
|
|
content?: string;
|
|
noteTags?: {
|
|
deleteMany: Record<string, never>;
|
|
create: Array<{
|
|
tag: {
|
|
connectOrCreate: {
|
|
where: { name: string };
|
|
create: { name: string };
|
|
};
|
|
};
|
|
}>;
|
|
};
|
|
} = {
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
// Ajouter les champs de base s'ils sont fournis
|
|
if (data.title !== undefined) {
|
|
updateData.title = data.title;
|
|
}
|
|
if (data.content !== undefined) {
|
|
updateData.content = data.content;
|
|
}
|
|
|
|
// Gérer les tags si fournis
|
|
if (data.tags !== undefined) {
|
|
updateData.noteTags = {
|
|
deleteMany: {}, // Supprimer tous les tags existants
|
|
create: data.tags.map((tagName) => ({
|
|
tag: {
|
|
connectOrCreate: {
|
|
where: { name: tagName },
|
|
create: { name: tagName },
|
|
},
|
|
},
|
|
})),
|
|
};
|
|
}
|
|
|
|
const note = await prisma.note.update({
|
|
where: { id: noteId },
|
|
data: updateData,
|
|
include: {
|
|
noteTags: {
|
|
include: {
|
|
tag: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
...note,
|
|
tags: note.noteTags.map((nt) => nt.tag.name),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Supprime une note
|
|
*/
|
|
async deleteNote(noteId: string, userId: string): Promise<void> {
|
|
// Vérifier que la note appartient à l'utilisateur
|
|
const existingNote = await prisma.note.findFirst({
|
|
where: {
|
|
id: noteId,
|
|
userId,
|
|
},
|
|
});
|
|
|
|
if (!existingNote) {
|
|
throw new Error('Note not found or access denied');
|
|
}
|
|
|
|
await prisma.note.delete({
|
|
where: { id: noteId },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Recherche des notes par titre ou contenu
|
|
*/
|
|
async searchNotes(userId: string, query: string): Promise<Note[]> {
|
|
const notes = await prisma.note.findMany({
|
|
where: {
|
|
userId,
|
|
OR: [{ title: { contains: query } }, { content: { contains: query } }],
|
|
},
|
|
orderBy: { updatedAt: 'desc' },
|
|
});
|
|
|
|
return notes;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques des notes d'un utilisateur
|
|
*/
|
|
async getNotesStats(userId: string): Promise<{
|
|
totalNotes: number;
|
|
totalWords: number;
|
|
lastUpdated: Date | null;
|
|
}> {
|
|
const notes = await prisma.note.findMany({
|
|
where: { userId },
|
|
select: {
|
|
content: true,
|
|
updatedAt: true,
|
|
},
|
|
});
|
|
|
|
const totalNotes = notes.length;
|
|
const totalWords = notes.reduce((acc, note) => {
|
|
return (
|
|
acc + note.content.split(/\s+/).filter((word) => word.length > 0).length
|
|
);
|
|
}, 0);
|
|
const lastUpdated =
|
|
notes.length > 0
|
|
? notes.reduce(
|
|
(latest, note) =>
|
|
note.updatedAt > latest ? note.updatedAt : latest,
|
|
notes[0].updatedAt
|
|
)
|
|
: null;
|
|
|
|
return {
|
|
totalNotes,
|
|
totalWords,
|
|
lastUpdated,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Instance singleton
|
|
export const notesService = new NotesService();
|