From d767ee9510a44f64ed32aa3513bd127b3b85a783 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 17 Apr 2025 00:49:15 +0200 Subject: [PATCH] Neah version calendar fix 3 debuger sec chance danger debug 7 --- .DS_Store | Bin 6148 -> 6148 bytes backup oldversion for help/.DS_Store | Bin 0 -> 6148 bytes .../api-calendar/route.ts | 166 ++++++++++++++++++ backup oldversion for help/flow.tsx | 129 ++++++++++++++ backup oldversion for help/prisma-news.ts | 13 ++ backup oldversion for help/prisma.ts | 14 ++ backup oldversion for help/route.ts | 144 +++++++++++++++ backup oldversion for help/utils.ts | 20 +++ lib/auth.ts | 150 +++++++--------- 9 files changed, 550 insertions(+), 86 deletions(-) create mode 100644 backup oldversion for help/.DS_Store create mode 100644 backup oldversion for help/api-calendar/route.ts create mode 100644 backup oldversion for help/flow.tsx create mode 100644 backup oldversion for help/prisma-news.ts create mode 100644 backup oldversion for help/prisma.ts create mode 100644 backup oldversion for help/route.ts create mode 100644 backup oldversion for help/utils.ts diff --git a/.DS_Store b/.DS_Store index 6934b8f40f4df978ccee310c65643390b78babcd..c8aec98cc9b987ff257861b604f5d412e141e607 100644 GIT binary patch literal 6148 zcmeHKQH#?+5T3o(yVOJIgMxP;EcjZmRj&$y`o}c@xbag#D}=vS%$V zpx_+iG*T)>myS0kJA-4uG4S6pz~63zLdq$R%iq7>2*xp;Qw5hKMJdH}K@%#dqy*y( z)=$6!IE6i>JX(}_l&T0RzeRbPl=E))huCX0Z|om<2i~FgA(*N~Fc0TdIS7k)JbH}- zBU<;v=uI+NjM}$fsB9i4*`&|`Nj$-n_ivLdR*QkEvbfZ-fpK^(uQh5PEtkC~{eDM2 zeAHcaCJ*u~2NHo}vgd0|Nsi1A_oVa!yiyeh!eyz&x=~Ll`8^%#g@Xz)+A< x43XR1&ep)VS%HI{Wn;r5#?9;;{2V}YHVblmXP(S2V#vra*@j1YbBxFeW&o#;7@PnA diff --git a/backup oldversion for help/.DS_Store b/backup oldversion for help/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..92eb809836cbfe7e70a60fd272c01b54e653a070 GIT binary patch literal 6148 zcmeHK&5ja55UvImn&2iJ)SdC>q$2S0IWTW8UR%Q;OH82Wo+&-J+6Jra;~R{ z$jm!>5I_QbkPvOLspD@{K=0iH+_e;9ID_^53t$}1Cux|d5IJAMG>IqEdi@*AZV%Z;?ggELW<>o4uk(#&F zOhyxv>)Qcd=H+hn@nX@qXtiqM>9hK>CKk_|jhblH&zH+Ge|Y-j#bx(48A<*K zG|4spYJ_vM;p2N(dIXbX0B>-XpyNzM2YYCkr z9C{8ji#USDY$~EnWv;|vHXZwg<9QA;KIpdLji#fqzN?R_OShHZIBCty_!JcdbBwkFHJgGK+U97?`UVx%w)8 cgl-J`g$@ushnYp}L8CtcMh0p~fj_FiPfBKX%K!iX literal 0 HcmV?d00001 diff --git a/backup oldversion for help/api-calendar/route.ts b/backup oldversion for help/api-calendar/route.ts new file mode 100644 index 0000000..ca4df27 --- /dev/null +++ b/backup oldversion for help/api-calendar/route.ts @@ -0,0 +1,166 @@ +import { NextResponse } from "next/server"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +// GET events +export async function GET(req: Request) { + try { + const session = await getServerSession(authOptions); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(req.url); + const start = searchParams.get("start"); + const end = searchParams.get("end"); + + // First get all calendars for the user + const calendars = await prisma.calendar.findMany({ + where: { + userId: session.user.id, + }, + }); + + // Then get events with calendar information + const events = await prisma.event.findMany({ + where: { + calendarId: { + in: calendars.map(cal => cal.id) + }, + ...(start && end + ? { + start: { + gte: new Date(start), + }, + end: { + lte: new Date(end), + }, + } + : {}), + }, + include: { + calendar: true, + }, + orderBy: { + start: "asc", + }, + }); + + // Map the events to include calendar color and name + const eventsWithCalendarInfo = events.map(event => ({ + ...event, + calendarColor: event.calendar.color, + calendarName: event.calendar.name, + calendar: undefined, // Remove the full calendar object + })); + + return NextResponse.json(eventsWithCalendarInfo); + } catch (error) { + console.error("Error fetching events:", error); + return NextResponse.json( + { error: "Erreur lors du chargement des événements" }, + { status: 500 } + ); + } +} + +// POST new event +export async function POST(req: Request) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json( + { error: "Non autorisé" }, + { status: 401 } + ); + } + + const data = await req.json(); + const { title, description, start, end, location, calendarId } = data; + + const event = await prisma.event.create({ + data: { + title, + description, + start: new Date(start), + end: new Date(end), + isAllDay: data.allDay || false, + location: location || null, + calendarId, + }, + }); + + return NextResponse.json(event); + } catch (error) { + console.error("Error creating event:", error); + return NextResponse.json( + { error: "Erreur lors de la création de l'événement" }, + { status: 500 } + ); + } +} + +// PUT update event +export async function PUT(req: Request) { + try { + const session = await getServerSession(authOptions); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await req.json(); + const { id, ...data } = body; + + const event = await prisma.event.update({ + where: { + id, + }, + data, + }); + + return NextResponse.json(event); + } catch (error) { + console.error("Error updating event:", error); + return NextResponse.json( + { error: "Error updating event" }, + { status: 500 } + ); + } +} + +// DELETE event +export async function DELETE(req: Request) { + try { + const session = await getServerSession(authOptions); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(req.url); + const id = searchParams.get("id"); + + if (!id) { + return NextResponse.json( + { error: "Event ID is required" }, + { status: 400 } + ); + } + + await prisma.event.delete({ + where: { + id, + }, + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error deleting event:", error); + return NextResponse.json( + { error: "Error deleting event" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/backup oldversion for help/flow.tsx b/backup oldversion for help/flow.tsx new file mode 100644 index 0000000..6503ffb --- /dev/null +++ b/backup oldversion for help/flow.tsx @@ -0,0 +1,129 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +interface Task { + id: string; + headline: string; + projectName: string; + projectId: number; + status: number; + dueDate: string | null; + milestone: string | null; + details: string | null; + createdOn: string; + editedOn: string | null; + assignedTo: number[]; +} + +export default function Flow() { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const getStatusLabel = (status: number): string => { + switch (status) { + case 1: + return 'New'; + case 2: + return 'In Progress'; + case 3: + return 'Done'; + case 4: + return 'In Progress'; + case 5: + return 'Done'; + default: + return 'Unknown'; + } + }; + + const getStatusColor = (status: number): string => { + switch (status) { + case 1: + return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'; + case 2: + case 4: + return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300'; + case 3: + case 5: + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300'; + } + }; + + useEffect(() => { + const fetchTasks = async () => { + setLoading(true); + setError(null); + try { + const response = await fetch('/api/leantime/tasks'); + if (!response.ok) { + throw new Error('Failed to fetch tasks'); + } + const data = await response.json(); + if (data.tasks && Array.isArray(data.tasks)) { + // Sort tasks by creation date (oldest first) + const sortedTasks = data.tasks.sort((a: Task, b: Task) => { + const dateA = new Date(a.createdOn).getTime(); + const dateB = new Date(b.createdOn).getTime(); + return dateA - dateB; + }); + setTasks(sortedTasks); + } else { + console.error('Invalid tasks data format:', data); + setError('Invalid tasks data format'); + } + } catch (err) { + console.error('Error fetching tasks:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + + fetchTasks(); + }, []); + + if (loading) { + return
Loading...
; + } + + if (error) { + return
Error: {error}
; + } + + if (tasks.length === 0) { + return
No tasks found
; + } + + return ( +
+ {tasks.map((task) => ( +
+
+
+

+ {task.headline} +

+

+ {task.projectName} +

+
+ + {getStatusLabel(task.status)} + +
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/backup oldversion for help/prisma-news.ts b/backup oldversion for help/prisma-news.ts new file mode 100644 index 0000000..26b690b --- /dev/null +++ b/backup oldversion for help/prisma-news.ts @@ -0,0 +1,13 @@ +import { PrismaClient } from '@prisma/client/news' + +const globalForPrisma = globalThis as unknown as { + prismaNews: PrismaClient | undefined; +} + +export const prismaNews = + globalForPrisma.prismaNews || + new PrismaClient({ + log: ['query'], + }) + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prismaNews = prismaNews \ No newline at end of file diff --git a/backup oldversion for help/prisma.ts b/backup oldversion for help/prisma.ts new file mode 100644 index 0000000..23adc83 --- /dev/null +++ b/backup oldversion for help/prisma.ts @@ -0,0 +1,14 @@ +// front/lib/prisma.ts +import { PrismaClient } from '@prisma/client' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +} + +export const prisma = + globalForPrisma.prisma || + new PrismaClient({ + log: ['query'], + }) + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma diff --git a/backup oldversion for help/route.ts b/backup oldversion for help/route.ts new file mode 100644 index 0000000..c7850b3 --- /dev/null +++ b/backup oldversion for help/route.ts @@ -0,0 +1,144 @@ +import NextAuth, { NextAuthOptions } from "next-auth"; +import KeycloakProvider from "next-auth/providers/keycloak"; + +declare module "next-auth" { + interface Session { + user: { + id: string; + name?: string | null; + email?: string | null; + image?: string | null; + username: string; + first_name: string; + last_name: string; + role: string[]; + }; + accessToken: string; + } + + interface JWT { + accessToken: string; + refreshToken: string; + accessTokenExpires: number; + role: string[]; + username: string; + first_name: string; + last_name: string; + } +} + +function getRequiredEnvVar(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} + +export const authOptions: NextAuthOptions = { + providers: [ + KeycloakProvider({ + clientId: getRequiredEnvVar("KEYCLOAK_CLIENT_ID"), + clientSecret: getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"), + issuer: getRequiredEnvVar("KEYCLOAK_ISSUER"), + profile(profile) { + return { + id: profile.sub, + name: profile.name ?? profile.preferred_username, + email: profile.email, + first_name: profile.given_name ?? '', + last_name: profile.family_name ?? '', + username: profile.preferred_username ?? profile.email?.split('@')[0] ?? '', + role: profile.groups ?? [], + } + }, + }), + ], + session: { + strategy: "jwt", + maxAge: 30 * 24 * 60 * 60, // 30 days + }, + callbacks: { + async jwt({ token, account, profile }) { + if (account && profile) { + token.accessToken = account.access_token; + token.refreshToken = account.refresh_token; + token.accessTokenExpires = account.expires_at! * 1000; + token.role = (profile as any).groups ?? []; + token.username = (profile as any).preferred_username ?? profile.email?.split('@')[0] ?? ''; + token.first_name = (profile as any).given_name ?? ''; + token.last_name = (profile as any).family_name ?? ''; + } + + // Return previous token if not expired + if (Date.now() < (token.accessTokenExpires as number)) { + return token; + } + + try { + const clientId = getRequiredEnvVar("KEYCLOAK_CLIENT_ID"); + const clientSecret = getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"); + + const response = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "refresh_token", + client_id: clientId, + client_secret: clientSecret, + refresh_token: token.refreshToken as string, + }), + } + ); + + const tokens = await response.json(); + + if (!response.ok) { + throw new Error("RefreshAccessTokenError"); + } + + return { + ...token, + accessToken: tokens.access_token, + refreshToken: tokens.refresh_token ?? token.refreshToken, + accessTokenExpires: Date.now() + tokens.expires_in * 1000, + }; + } catch (error) { + return { + ...token, + error: "RefreshAccessTokenError", + }; + } + }, + async session({ session, token }) { + if (token.error) { + throw new Error("RefreshAccessTokenError"); + } + + session.accessToken = token.accessToken; + session.user = { + ...session.user, + id: token.sub as string, + first_name: token.first_name ?? '', + last_name: token.last_name ?? '', + username: token.username ?? '', + role: token.role ?? [], + }; + + return session; + } + }, + pages: { + signIn: '/signin', + error: '/signin', + }, + debug: process.env.NODE_ENV === 'development', +}; + +const handler = NextAuth(authOptions); +export { handler as GET, handler as POST }; + diff --git a/backup oldversion for help/utils.ts b/backup oldversion for help/utils.ts new file mode 100644 index 0000000..b19c6ce --- /dev/null +++ b/backup oldversion for help/utils.ts @@ -0,0 +1,20 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function formatDate(dateString: string): string { + try { + const date = new Date(dateString); + return new Intl.DateTimeFormat('en-US', { + month: '2-digit', + day: '2-digit', + year: 'numeric' + }).format(date); + } catch { + return ''; + } +} + diff --git a/lib/auth.ts b/lib/auth.ts index a9dfcee..5ae2268 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,123 +1,96 @@ -import { NextAuthOptions } from 'next-auth'; -import KeycloakProvider from 'next-auth/providers/keycloak'; +import NextAuth, { NextAuthOptions } from "next-auth"; +import KeycloakProvider from "next-auth/providers/keycloak"; -declare module 'next-auth' { - interface User { - id: string; - email: string; - name?: string | null; - role: string[]; - first_name: string; - last_name: string; - username: string; - } +declare module "next-auth" { interface Session { user: { id: string; - email: string; name?: string | null; - role: string[]; + email?: string | null; + image?: string | null; + username: string; first_name: string; last_name: string; - username: string; + role: string[]; }; accessToken: string; - refreshToken: string; } - interface Profile { - sub?: string; - email?: string; - name?: string; - roles?: string[]; - given_name?: string; - family_name?: string; - preferred_username?: string; - } -} -declare module 'next-auth/jwt' { interface JWT { - id: string; - email: string; - name?: string; - role: string[]; - first_name: string; - last_name: string; - username: string; accessToken: string; refreshToken: string; accessTokenExpires: number; - error?: string; + role: string[]; + username: string; + first_name: string; + last_name: string; } } +function getRequiredEnvVar(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} + export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ - clientId: process.env.KEYCLOAK_CLIENT_ID!, - clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!, - issuer: process.env.KEYCLOAK_ISSUER, - authorization: { - params: { - scope: 'openid email profile', - response_type: 'code', + clientId: getRequiredEnvVar("KEYCLOAK_CLIENT_ID"), + clientSecret: getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"), + issuer: getRequiredEnvVar("KEYCLOAK_ISSUER"), + profile(profile) { + return { + id: profile.sub, + name: profile.name ?? profile.preferred_username, + email: profile.email, + first_name: profile.given_name ?? '', + last_name: profile.family_name ?? '', + username: profile.preferred_username ?? profile.email?.split('@')[0] ?? '', + role: profile.groups ?? [], } }, }), ], - debug: false, session: { - strategy: 'jwt', + strategy: "jwt", maxAge: 30 * 24 * 60 * 60, // 30 days }, - pages: { - signIn: '/signin', - error: '/signin', - }, callbacks: { async jwt({ token, account, profile }) { if (account && profile) { - if (!profile.sub) { - throw new Error('No user ID (sub) provided by Keycloak'); - } - if (!account.access_token || !account.refresh_token || !account.expires_at) { - throw new Error('Missing required token fields from Keycloak'); - } - token.id = profile.sub; - token.email = profile.email || ''; - token.name = profile.name; - token.role = profile.roles || ['user']; - token.first_name = profile.given_name || ''; - token.last_name = profile.family_name || ''; - token.username = profile.preferred_username || ''; - token.accessToken = account.access_token; - token.refreshToken = account.refresh_token; - token.accessTokenExpires = account.expires_at * 1000; + token.accessToken = account.access_token!; + token.refreshToken = account.refresh_token!; + token.accessTokenExpires = account.expires_at! * 1000; + token.role = (profile as any).groups ?? []; + token.username = (profile as any).preferred_username ?? profile.email?.split('@')[0] ?? ''; + token.first_name = (profile as any).given_name ?? ''; + token.last_name = (profile as any).family_name ?? ''; } // Return previous token if not expired - if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) { + if (Date.now() < (token.accessTokenExpires as number)) { return token; } - // Token expired, try to refresh - if (!token.refreshToken) { - throw new Error('No refresh token available'); - } - try { + const clientId = getRequiredEnvVar("KEYCLOAK_CLIENT_ID"); + const clientSecret = getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"); + const response = await fetch( `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ - grant_type: 'refresh_token', - client_id: process.env.KEYCLOAK_CLIENT_ID!, - client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, - refresh_token: token.refreshToken, + grant_type: "refresh_token", + client_id: clientId, + client_secret: clientSecret, + refresh_token: token.refreshToken as string, }), } ); @@ -125,7 +98,7 @@ export const authOptions: NextAuthOptions = { const tokens = await response.json(); if (!response.ok) { - throw new Error('RefreshAccessTokenError'); + throw new Error("RefreshAccessTokenError"); } return { @@ -137,26 +110,31 @@ export const authOptions: NextAuthOptions = { } catch (error) { return { ...token, - error: 'RefreshAccessTokenError', + error: "RefreshAccessTokenError", }; } }, async session({ session, token }) { if (token.error) { - throw new Error('RefreshAccessTokenError'); + throw new Error("RefreshAccessTokenError"); } + session.accessToken = token.accessToken; session.user = { - id: token.sub ?? token.id ?? '', - email: token.email ?? '', - name: token.name ?? '', - role: token.role ?? ['user'], + ...session.user, + id: token.sub as string, first_name: token.first_name ?? '', last_name: token.last_name ?? '', - username: token.username ?? '' + username: token.username ?? '', + role: token.role ?? [], }; - + return session; - }, + } }, + pages: { + signIn: '/signin', + error: '/signin', + }, + debug: process.env.NODE_ENV === 'development', }; \ No newline at end of file