feat(DailyCheckbox): associate checkboxes with users and enhance daily view functionality
- Added userId field to DailyCheckbox model for user association. - Updated DailyService methods to handle user-specific checkbox retrieval and management. - Integrated user authentication checks in API routes and actions for secure access to daily data. - Enhanced DailyPage to display user-specific daily views, ensuring proper session handling. - Updated client and service interfaces to reflect changes in data structure.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
-- Migration pour ajouter userId aux DailyCheckbox
|
||||
-- et associer les entrées existantes au premier utilisateur
|
||||
|
||||
-- 1. Ajouter la colonne userId (nullable temporairement)
|
||||
ALTER TABLE "daily_checkboxes" ADD COLUMN "userId" TEXT;
|
||||
|
||||
-- 2. Migrer les données existantes vers le premier utilisateur
|
||||
-- (on suppose qu'il y a au moins un utilisateur dans la table users)
|
||||
UPDATE "daily_checkboxes"
|
||||
SET "userId" = (SELECT id FROM "users" LIMIT 1)
|
||||
WHERE "userId" IS NULL;
|
||||
|
||||
-- 3. Créer un index sur userId pour les performances
|
||||
CREATE INDEX "daily_checkboxes_userId_idx" ON "daily_checkboxes"("userId");
|
||||
|
||||
-- Note: La contrainte de clé étrangère sera gérée par Prisma
|
||||
-- SQLite ne supporte pas les contraintes de clé étrangère dans ALTER TABLE
|
||||
@@ -22,6 +22,7 @@ model User {
|
||||
updatedAt DateTime @updatedAt
|
||||
preferences UserPreferences?
|
||||
notes Note[]
|
||||
dailyCheckboxes DailyCheckbox[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -98,11 +99,14 @@ model DailyCheckbox {
|
||||
type String @default("task")
|
||||
order Int @default(0)
|
||||
taskId String?
|
||||
userId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
task Task? @relation(fields: [taskId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([date])
|
||||
@@index([userId])
|
||||
@@map("daily_checkboxes")
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
parseDate,
|
||||
normalizeDate,
|
||||
} from '@/lib/date-utils';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
|
||||
/**
|
||||
* Toggle l'état d'une checkbox
|
||||
@@ -23,6 +25,11 @@ export async function toggleCheckbox(checkboxId: string): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return { success: false, error: 'Non authentifié' };
|
||||
}
|
||||
|
||||
// Nous devons d'abord récupérer la checkbox pour connaître son état actuel
|
||||
// En absence de getCheckboxById, nous allons essayer de la trouver via une vue daily
|
||||
// Pour l'instant, nous allons simplement toggle via updateCheckbox
|
||||
@@ -30,7 +37,7 @@ export async function toggleCheckbox(checkboxId: string): Promise<{
|
||||
|
||||
// Récupérer toutes les checkboxes d'aujourd'hui et hier pour trouver celle à toggle
|
||||
const today = getToday();
|
||||
const dailyView = await dailyService.getDailyView(today);
|
||||
const dailyView = await dailyService.getDailyView(today, session.user.id);
|
||||
|
||||
let checkbox = dailyView.today.find((cb) => cb.id === checkboxId);
|
||||
if (!checkbox) {
|
||||
@@ -70,8 +77,14 @@ export async function addTodayCheckbox(
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return { success: false, error: 'Non authentifié' };
|
||||
}
|
||||
|
||||
const newCheckbox = await dailyService.addCheckbox({
|
||||
date: getToday(),
|
||||
userId: session.user.id,
|
||||
text: content,
|
||||
type: type || 'task',
|
||||
taskId,
|
||||
@@ -101,10 +114,16 @@ export async function addYesterdayCheckbox(
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return { success: false, error: 'Non authentifié' };
|
||||
}
|
||||
|
||||
const yesterday = getPreviousWorkday(getToday());
|
||||
|
||||
const newCheckbox = await dailyService.addCheckbox({
|
||||
date: yesterday,
|
||||
userId: session.user.id,
|
||||
text: content,
|
||||
type: type || 'task',
|
||||
taskId,
|
||||
@@ -180,10 +199,16 @@ export async function addTodoToTask(
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return { success: false, error: 'Non authentifié' };
|
||||
}
|
||||
|
||||
const targetDate = normalizeDate(date || getToday());
|
||||
|
||||
const checkboxData: CreateDailyCheckboxData = {
|
||||
date: targetDate,
|
||||
userId: session.user.id,
|
||||
text: text.trim(),
|
||||
type: 'task',
|
||||
taskId: taskId,
|
||||
|
||||
@@ -6,12 +6,19 @@ import {
|
||||
isValidAPIDate,
|
||||
createDateFromParts,
|
||||
} from '@/lib/date-utils';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
|
||||
/**
|
||||
* API route pour récupérer la vue daily (hier + aujourd'hui)
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const action = searchParams.get('action');
|
||||
@@ -20,7 +27,10 @@ export async function GET(request: Request) {
|
||||
if (action === 'history') {
|
||||
// Récupérer l'historique
|
||||
const limit = parseInt(searchParams.get('limit') || '30');
|
||||
const history = await dailyService.getCheckboxHistory(limit);
|
||||
const history = await dailyService.getCheckboxHistory(
|
||||
session.user.id,
|
||||
limit
|
||||
);
|
||||
return NextResponse.json(history);
|
||||
}
|
||||
|
||||
@@ -55,7 +65,10 @@ export async function GET(request: Request) {
|
||||
targetDate = getToday();
|
||||
}
|
||||
|
||||
const dailyView = await dailyService.getDailyView(targetDate);
|
||||
const dailyView = await dailyService.getDailyView(
|
||||
targetDate,
|
||||
session.user.id
|
||||
);
|
||||
return NextResponse.json(dailyView);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération du daily:', error);
|
||||
@@ -71,6 +84,10 @@ export async function GET(request: Request) {
|
||||
*/
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||
}
|
||||
const body = await request.json();
|
||||
|
||||
// Validation des données
|
||||
@@ -100,6 +117,7 @@ export async function POST(request: Request) {
|
||||
|
||||
const checkbox = await dailyService.addCheckbox({
|
||||
date,
|
||||
userId: session.user.id,
|
||||
text: body.text,
|
||||
type: body.type,
|
||||
taskId: body.taskId,
|
||||
|
||||
@@ -3,6 +3,8 @@ import { DailyPageClient } from './DailyPageClient';
|
||||
import { dailyService } from '@/services/task-management/daily';
|
||||
import { DeadlineAnalyticsService } from '@/services/analytics/deadline-analytics';
|
||||
import { getToday } from '@/lib/date-utils';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
|
||||
// Force dynamic rendering (no static generation)
|
||||
export const dynamic = 'force-dynamic';
|
||||
@@ -13,13 +15,31 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
export default async function DailyPage() {
|
||||
// Récupérer la session utilisateur
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--background)] flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-[var(--foreground)] mb-4">
|
||||
Non autorisé
|
||||
</h1>
|
||||
<p className="text-[var(--muted-foreground)]">
|
||||
Vous devez être connecté pour accéder à la page daily.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Récupérer les données côté serveur
|
||||
const today = getToday();
|
||||
|
||||
try {
|
||||
const [dailyView, dailyDates, deadlineMetrics, pendingTasks] =
|
||||
await Promise.all([
|
||||
dailyService.getDailyView(today),
|
||||
dailyService.getDailyView(today, session.user.id),
|
||||
dailyService.getDailyDates(),
|
||||
DeadlineAnalyticsService.getDeadlineMetrics().catch(() => null), // Graceful fallback
|
||||
dailyService
|
||||
|
||||
@@ -17,6 +17,7 @@ interface ApiCheckbox {
|
||||
type: 'task' | 'meeting';
|
||||
order: number;
|
||||
taskId?: string;
|
||||
userId: string;
|
||||
task?: Task;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TfsConfig } from '@/services/integrations/tfs/tfs';
|
||||
import { Theme } from './ui-config';
|
||||
import { User } from '@/services/users';
|
||||
|
||||
// Réexporter Theme pour les autres modules
|
||||
export type { Theme };
|
||||
@@ -438,7 +439,9 @@ export interface DailyCheckbox {
|
||||
type: DailyCheckboxType;
|
||||
order: number;
|
||||
taskId?: string;
|
||||
userId: string;
|
||||
task?: Task; // Relation optionnelle vers une tâche
|
||||
user?: User; // Relation vers l'utilisateur
|
||||
isArchived?: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -447,6 +450,7 @@ export interface DailyCheckbox {
|
||||
// Interface pour créer/modifier une checkbox
|
||||
export interface CreateDailyCheckboxData {
|
||||
date: Date;
|
||||
userId: string;
|
||||
text: string;
|
||||
type?: DailyCheckboxType;
|
||||
taskId?: string;
|
||||
|
||||
@@ -26,7 +26,7 @@ export class DailyService {
|
||||
/**
|
||||
* Récupère la vue daily pour une date donnée (checkboxes d'hier et d'aujourd'hui)
|
||||
*/
|
||||
async getDailyView(date: Date): Promise<DailyView> {
|
||||
async getDailyView(date: Date, userId: string): Promise<DailyView> {
|
||||
// Normaliser la date (début de journée)
|
||||
const today = normalizeDate(date);
|
||||
|
||||
@@ -35,8 +35,8 @@ export class DailyService {
|
||||
|
||||
// Récupérer les checkboxes des deux jours
|
||||
const [yesterdayCheckboxes, todayCheckboxes] = await Promise.all([
|
||||
this.getCheckboxesByDate(yesterday),
|
||||
this.getCheckboxesByDate(today),
|
||||
this.getCheckboxesByDate(yesterday, userId),
|
||||
this.getCheckboxesByDate(today, userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -47,15 +47,21 @@ export class DailyService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère toutes les checkboxes d'une date donnée
|
||||
* Récupère toutes les checkboxes d'une date donnée pour un utilisateur
|
||||
*/
|
||||
async getCheckboxesByDate(date: Date): Promise<DailyCheckbox[]> {
|
||||
async getCheckboxesByDate(
|
||||
date: Date,
|
||||
userId: string
|
||||
): Promise<DailyCheckbox[]> {
|
||||
// Normaliser la date (début de journée)
|
||||
const normalizedDate = normalizeDate(date);
|
||||
|
||||
const checkboxes = await prisma.dailyCheckbox.findMany({
|
||||
where: { date: normalizedDate },
|
||||
include: { task: true },
|
||||
where: {
|
||||
date: normalizedDate,
|
||||
userId: userId,
|
||||
},
|
||||
include: { task: true, user: true },
|
||||
orderBy: { order: 'asc' },
|
||||
});
|
||||
|
||||
@@ -83,10 +89,11 @@ export class DailyService {
|
||||
text: data.text.trim(),
|
||||
type: data.type ?? 'task',
|
||||
taskId: data.taskId,
|
||||
userId: data.userId,
|
||||
order,
|
||||
isChecked: data.isChecked ?? false,
|
||||
},
|
||||
include: { task: true },
|
||||
include: { task: true, user: true },
|
||||
});
|
||||
|
||||
return this.mapPrismaCheckbox(checkbox);
|
||||
@@ -117,7 +124,7 @@ export class DailyService {
|
||||
const checkbox = await prisma.dailyCheckbox.update({
|
||||
where: { id: checkboxId },
|
||||
data: updateData,
|
||||
include: { task: true },
|
||||
include: { task: true, user: true },
|
||||
});
|
||||
|
||||
return this.mapPrismaCheckbox(checkbox);
|
||||
@@ -167,7 +174,7 @@ export class DailyService {
|
||||
contains: query,
|
||||
},
|
||||
},
|
||||
include: { task: true },
|
||||
include: { task: true, user: true },
|
||||
orderBy: { date: 'desc' },
|
||||
take: limit,
|
||||
});
|
||||
@@ -179,10 +186,12 @@ export class DailyService {
|
||||
* Récupère l'historique des checkboxes (groupées par date)
|
||||
*/
|
||||
async getCheckboxHistory(
|
||||
userId: string,
|
||||
limit: number = 30
|
||||
): Promise<{ date: Date; checkboxes: DailyCheckbox[] }[]> {
|
||||
// Récupérer les dates distinctes des dernières checkboxes
|
||||
// Récupérer les dates distinctes des dernières checkboxes pour cet utilisateur
|
||||
const distinctDates = await prisma.dailyCheckbox.findMany({
|
||||
where: { userId },
|
||||
select: { date: true },
|
||||
distinct: ['date'],
|
||||
orderBy: { date: 'desc' },
|
||||
@@ -191,7 +200,7 @@ export class DailyService {
|
||||
|
||||
const history = [];
|
||||
for (const { date } of distinctDates) {
|
||||
const checkboxes = await this.getCheckboxesByDate(date);
|
||||
const checkboxes = await this.getCheckboxesByDate(date, userId);
|
||||
if (checkboxes.length > 0) {
|
||||
history.push({ date, checkboxes });
|
||||
}
|
||||
@@ -203,19 +212,21 @@ export class DailyService {
|
||||
/**
|
||||
* Récupère la vue daily d'aujourd'hui
|
||||
*/
|
||||
async getTodaysDailyView(): Promise<DailyView> {
|
||||
return this.getDailyView(getToday());
|
||||
async getTodaysDailyView(userId: string): Promise<DailyView> {
|
||||
return this.getDailyView(getToday(), userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une checkbox pour aujourd'hui
|
||||
*/
|
||||
async addTodayCheckbox(
|
||||
userId: string,
|
||||
text: string,
|
||||
taskId?: string
|
||||
): Promise<DailyCheckbox> {
|
||||
return this.addCheckbox({
|
||||
date: getToday(),
|
||||
userId,
|
||||
text,
|
||||
taskId,
|
||||
});
|
||||
@@ -225,11 +236,13 @@ export class DailyService {
|
||||
* Ajoute une checkbox pour hier
|
||||
*/
|
||||
async addYesterdayCheckbox(
|
||||
userId: string,
|
||||
text: string,
|
||||
taskId?: string
|
||||
): Promise<DailyCheckbox> {
|
||||
return this.addCheckbox({
|
||||
date: getYesterday(),
|
||||
userId,
|
||||
text,
|
||||
taskId,
|
||||
});
|
||||
@@ -239,7 +252,9 @@ export class DailyService {
|
||||
* Mappe une checkbox Prisma vers notre interface
|
||||
*/
|
||||
private mapPrismaCheckbox(
|
||||
checkbox: Prisma.DailyCheckboxGetPayload<{ include: { task: true } }>
|
||||
checkbox: Prisma.DailyCheckboxGetPayload<{
|
||||
include: { task: true; user: true };
|
||||
}>
|
||||
): DailyCheckbox {
|
||||
return {
|
||||
id: checkbox.id,
|
||||
@@ -249,6 +264,7 @@ export class DailyService {
|
||||
type: checkbox.type as DailyCheckboxType,
|
||||
order: checkbox.order,
|
||||
taskId: checkbox.taskId || undefined,
|
||||
userId: checkbox.userId,
|
||||
task: checkbox.task
|
||||
? {
|
||||
id: checkbox.task.id,
|
||||
@@ -334,7 +350,7 @@ export class DailyService {
|
||||
|
||||
const checkboxes = await prisma.dailyCheckbox.findMany({
|
||||
where: whereConditions,
|
||||
include: { task: true },
|
||||
include: { task: true, user: true },
|
||||
orderBy: [{ date: 'desc' }, { order: 'asc' }],
|
||||
...(options?.limit ? { take: options.limit } : {}),
|
||||
});
|
||||
@@ -356,7 +372,7 @@ export class DailyService {
|
||||
?.text + ' [ARCHIVÉ]',
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
include: { task: true },
|
||||
include: { task: true, user: true },
|
||||
});
|
||||
|
||||
return this.mapPrismaCheckbox(checkbox);
|
||||
@@ -400,7 +416,7 @@ export class DailyService {
|
||||
order: newOrder,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
include: { task: true },
|
||||
include: { task: true, user: true },
|
||||
});
|
||||
|
||||
return this.mapPrismaCheckbox(updatedCheckbox);
|
||||
|
||||
@@ -237,6 +237,7 @@ export class TasksService {
|
||||
type: checkbox.type as DailyCheckboxType,
|
||||
order: checkbox.order,
|
||||
taskId: checkbox.taskId ?? undefined,
|
||||
userId: checkbox.userId,
|
||||
task: checkbox.task
|
||||
? {
|
||||
id: checkbox.task.id,
|
||||
|
||||
Reference in New Issue
Block a user