From 73419f468e95bd40e4b28cc6e74a4478e873c30f Mon Sep 17 00:00:00 2001 From: Linrador <68631622+Linrador@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:28:24 +0100 Subject: [PATCH] updated --- app/api/auth/[...nextauth]/route.ts | 82 ++++ app/layout.tsx | 3 +- app/login/page.tsx | 21 + app/providers.tsx | 14 + app/ui/Button.tsx | 52 --- app/ui/Tabs.tsx | 52 --- components/auth/LoginForm.tsx | 271 ++++++++++++ components/ui/Button.tsx | 138 ++++++ components/ui/Tabs.tsx | 62 +++ middleware.ts | 13 + package-lock.json | 417 ++++++++++++++++-- package.json | 9 +- prisma/create-test-user.ts | 41 ++ prisma/dev.db | Bin 12288 -> 114688 bytes .../migration.sql | 97 ++++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 128 +++++- 17 files changed, 1254 insertions(+), 149 deletions(-) create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/login/page.tsx create mode 100644 app/providers.tsx delete mode 100644 app/ui/Button.tsx delete mode 100644 app/ui/Tabs.tsx create mode 100644 components/auth/LoginForm.tsx create mode 100644 components/ui/Button.tsx create mode 100644 components/ui/Tabs.tsx create mode 100644 middleware.ts create mode 100644 prisma/create-test-user.ts create mode 100644 prisma/migrations/20251114101117_add_user_password/migration.sql create mode 100644 prisma/migrations/migration_lock.toml diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..c76160b --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,82 @@ +// app/api/auth/[...nextauth]/route.ts +import NextAuth from 'next-auth'; +import CredentialsProvider from 'next-auth/providers/credentials'; +import { PrismaClient } from '@prisma/client'; +import { compare } from 'bcryptjs'; + +const prisma = new PrismaClient(); + +const handler = NextAuth({ + providers: [ + CredentialsProvider({ + name: 'Credentials', + credentials: { + email: { + label: 'Benutzername oder E-Mail', + type: 'text', + }, + password: { + label: 'Passwort', + type: 'password', + }, + }, + async authorize(credentials) { + if (!credentials?.email || !credentials.password) { + return null; + } + + const identifier = credentials.email.trim(); + + // User per E-Mail ODER Benutzername suchen + const user = await prisma.user.findFirst({ + where: { + OR: [ + { email: identifier }, + { username: identifier }, + ], + }, + }); + + if (!user || !user.passwordHash) { + return null; + } + + const isValid = await compare(credentials.password, user.passwordHash); + if (!isValid) { + return null; + } + + // Minimal-Userobjekt für NextAuth zurückgeben + return { + id: user.id, + name: user.name ?? user.username ?? user.email, + email: user.email, + }; + }, + }), + ], + pages: { + signIn: '/login', + }, + session: { + strategy: 'jwt', + }, + callbacks: { + async jwt({ token, user }) { + // beim Login User-ID in den Token schreiben + if (user) { + token.id = user.id; + } + return token; + }, + async session({ session, token }) { + // ID aus dem Token wieder in die Session kopieren + if (session.user && token.id) { + (session.user as any).id = token.id; + } + return session; + }, + }, +}); + +export { handler as GET, handler as POST }; diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..aa9f3f1 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import Providers from "./providers"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,7 +28,7 @@ export default function RootLayout({ - {children} + {children} ); diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..51fac7c --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,21 @@ +// app/login/page.tsx (oder eine Client-Komponente) +'use client'; + +import { signIn } from 'next-auth/react'; +import LoginForm from '@/components/auth/LoginForm'; + +export default function LoginPage() { + return ( + { + // NextAuth kümmert sich um Redirect (inkl. callbackUrl) + await signIn('credentials', { + email, + password, + // callbackUrl: '/', // optional, sonst nimmt er den ursprünglichen Pfad + }); + }} + /> + ); +} diff --git a/app/providers.tsx b/app/providers.tsx new file mode 100644 index 0000000..f89b792 --- /dev/null +++ b/app/providers.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { SessionProvider } from 'next-auth/react'; +import type { Session } from 'next-auth'; + +export default function Providers({ + children, + session, +}: { + children: React.ReactNode; + session?: Session | null; +}) { + return {children}; +} diff --git a/app/ui/Button.tsx b/app/ui/Button.tsx deleted file mode 100644 index bb692ca..0000000 --- a/app/ui/Button.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client' - -import React from 'react' - -type ButtonVariant = 'primary' | 'secondary' | 'outline' -type ButtonSize = 'sm' | 'md' | 'lg' - -export type ButtonProps = React.ButtonHTMLAttributes & { - variant?: ButtonVariant - size?: ButtonSize -} - -export function Button({ - variant = 'primary', - size = 'md', - className, - children, - ...props -}: ButtonProps) { - const baseClasses = - 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed dark:ring-offset-gray-900' - - const variantClasses: Record = { - primary: - 'bg-indigo-600 text-white hover:bg-indigo-700 active:bg-indigo-800 dark:bg-indigo-500 dark:hover:bg-indigo-400', - secondary: - 'bg-gray-100 text-gray-900 hover:bg-gray-200 active:bg-gray-300 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700', - outline: - 'border border-gray-300 text-gray-900 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-600 dark:text-gray-100 dark:hover:bg-gray-800', - } - - const sizeClasses: Record = { - sm: 'px-3 py-1.5 text-xs', - md: 'px-4 py-2 text-sm', - lg: 'px-5 py-2.5 text-sm', - } - - const classes = [ - baseClasses, - variantClasses[variant], - sizeClasses[size], - className, - ] - .filter(Boolean) - .join(' ') - - return ( - - ) -} diff --git a/app/ui/Tabs.tsx b/app/ui/Tabs.tsx deleted file mode 100644 index bb692ca..0000000 --- a/app/ui/Tabs.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client' - -import React from 'react' - -type ButtonVariant = 'primary' | 'secondary' | 'outline' -type ButtonSize = 'sm' | 'md' | 'lg' - -export type ButtonProps = React.ButtonHTMLAttributes & { - variant?: ButtonVariant - size?: ButtonSize -} - -export function Button({ - variant = 'primary', - size = 'md', - className, - children, - ...props -}: ButtonProps) { - const baseClasses = - 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed dark:ring-offset-gray-900' - - const variantClasses: Record = { - primary: - 'bg-indigo-600 text-white hover:bg-indigo-700 active:bg-indigo-800 dark:bg-indigo-500 dark:hover:bg-indigo-400', - secondary: - 'bg-gray-100 text-gray-900 hover:bg-gray-200 active:bg-gray-300 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700', - outline: - 'border border-gray-300 text-gray-900 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-600 dark:text-gray-100 dark:hover:bg-gray-800', - } - - const sizeClasses: Record = { - sm: 'px-3 py-1.5 text-xs', - md: 'px-4 py-2 text-sm', - lg: 'px-5 py-2.5 text-sm', - } - - const classes = [ - baseClasses, - variantClasses[variant], - sizeClasses[size], - className, - ] - .filter(Boolean) - .join(' ') - - return ( - - ) -} diff --git a/components/auth/LoginForm.tsx b/components/auth/LoginForm.tsx new file mode 100644 index 0000000..052e0d9 --- /dev/null +++ b/components/auth/LoginForm.tsx @@ -0,0 +1,271 @@ +// src/components/auth/LoginForm.tsx +'use client'; + +import * as React from 'react'; +import Button from '@/components/ui/Button'; + +type LoginValues = { + email: string; + password: string; + rememberMe: boolean; +}; + +type SocialProvider = 'google' | 'github'; + +export interface LoginFormProps { + title?: string; + subtitle?: string; + onSubmit?: (values: LoginValues) => void | Promise; + isSubmitting?: boolean; + errorMessage?: string | null; + showRememberMe?: boolean; + showSocialLogin?: boolean; + onSocialClick?: (provider: SocialProvider) => void; +} + +const LoginForm: React.FC = ({ + title = 'Bitte melde dich an', + subtitle, + onSubmit, + isSubmitting, + errorMessage, + showRememberMe = true, + showSocialLogin = true, + onSocialClick, +}) => { + const [form, setForm] = React.useState({ + email: '', + password: '', + rememberMe: false, + }); + + const handleChange = + (field: keyof LoginValues) => + (e: React.ChangeEvent) => { + const value = field === 'rememberMe' ? e.target.checked : e.target.value; + setForm((prev) => ({ ...prev, [field]: value as never })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!onSubmit) return; + await onSubmit(form); + }; + + return ( +
+ {/* Header */} +
+ Your Company + Your Company +

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+ + {/* Card */} +
+
+
+ {/* Benutzername / E-Mail */} +
+ +
+ +
+
+ + {/* Password */} +
+ +
+ +
+
+ + {/* Remember + Forgot */} +
+ {showRememberMe && ( +
+
+
+ + + + + +
+
+ +
+ )} + + +
+ + {/* Error */} + {errorMessage && ( +

+ {errorMessage} +

+ )} + + {/* Submit */} +
+ +
+
+ + {/* Social login */} + {showSocialLogin && ( +
+
+
+

+ Oder anmelden mit +

+
+
+ +
+ {/* Google */} + + + {/* GitHub */} + +
+
+ )} +
+
+
+ ); +}; + +export default LoginForm; diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx new file mode 100644 index 0000000..7e1fda5 --- /dev/null +++ b/components/ui/Button.tsx @@ -0,0 +1,138 @@ +// /components/ui/Button.tsx + +// src/components/Button.tsx +import * as React from 'react'; +import clsx from 'clsx'; + +export type ButtonVariant = 'primary' | 'secondary' | 'soft'; +export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +export type ButtonShape = 'default' | 'pill' | 'circle'; + +export interface ButtonProps + extends React.ButtonHTMLAttributes { + variant?: ButtonVariant; + size?: ButtonSize; + shape?: ButtonShape; // "default" (normal), "pill" (rounded-full), "circle" (nur Icon) + icon?: React.ReactNode; + iconPosition?: 'leading' | 'trailing'; + fullWidth?: boolean; +} + +const baseClasses = + 'inline-flex items-center justify-center font-semibold shadow-xs ' + + 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500 ' + + 'disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150'; + +// Farben / Styles wie in deinen Beispielen +const variantClasses: Record = { + primary: + 'bg-indigo-600 text-white hover:bg-indigo-500 ' + + 'dark:bg-indigo-500 dark:text-white dark:hover:bg-indigo-400 dark:shadow-none', + secondary: + 'bg-white text-gray-900 inset-ring inset-ring-gray-300 hover:bg-gray-50 ' + + 'dark:bg-white/10 dark:text-white dark:shadow-none dark:inset-ring-white/5 dark:hover:bg-white/20', + soft: + 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100 ' + + 'dark:bg-indigo-500/20 dark:text-indigo-400 dark:shadow-none dark:hover:bg-indigo-500/30', +}; + +// Größen wie in deinen Snippets (rectangular) +const sizeClasses: Record = { + xs: 'rounded-sm px-2 py-1 text-xs', + sm: 'rounded-sm px-2 py-1 text-sm', + md: 'rounded-md px-2.5 py-1.5 text-sm', + lg: 'rounded-md px-3 py-2 text-sm', + xl: 'rounded-md px-3.5 py-2.5 text-sm', +}; + +// Pill / "rounded primary/secondary buttons" Größen +const pillSizeClasses: Record = { + xs: 'rounded-full px-2.5 py-1 text-xs', + sm: 'rounded-full px-2.5 py-1 text-sm', + md: 'rounded-full px-3 py-1.5 text-sm', + lg: 'rounded-full px-3.5 py-2 text-sm', + xl: 'rounded-full px-4 py-2.5 text-sm', +}; + +// Circular Buttons – nur Icon +const circleSizeClasses: Record = { + xs: 'rounded-full p-1', + sm: 'rounded-full p-1.5', + md: 'rounded-full p-2', + lg: 'rounded-full p-2', + xl: 'rounded-full p-2.5', +}; + +function getSizeClasses(size: ButtonSize, shape: ButtonShape): string { + switch (shape) { + case 'pill': + return pillSizeClasses[size]; + case 'circle': + return circleSizeClasses[size]; + default: + return sizeClasses[size]; + } +} + +const Button = React.forwardRef( + ( + { + variant = 'primary', + size = 'md', + shape = 'default', + icon, + iconPosition = 'leading', + fullWidth, + className, + children, + type = 'button', + ...props + }, + ref + ) => { + const hasIcon = !!icon; + const hasLabel = React.Children.count(children) > 0; + + const gapClasses = + hasIcon && hasLabel + ? size === 'xl' + ? 'gap-x-2' + : 'gap-x-1.5' + : ''; + + return ( + + ); + } +); + +Button.displayName = 'Button'; + +export default Button; diff --git a/components/ui/Tabs.tsx b/components/ui/Tabs.tsx new file mode 100644 index 0000000..8c3cb85 --- /dev/null +++ b/components/ui/Tabs.tsx @@ -0,0 +1,62 @@ +// /components/ui/Tabs.tsx + +'use client' + +import * as React from 'react' + +export type TabItem = { + id: string + label: string +} + +export type TabsProps = { + tabs: TabItem[] + defaultTabId?: string + onChange?(id: string): void + className?: string +} + +export function Tabs({ tabs, defaultTabId, onChange, className }: TabsProps) { + const [activeId, setActiveId] = React.useState( + defaultTabId ?? (tabs[0]?.id ?? ''), + ) + + const handleClick = (id: string) => { + setActiveId(id) + onChange?.(id) + } + + return ( +
+ +
+ ) +} diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..7f44fb0 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,13 @@ +// middleware.ts +export { default } from 'next-auth/middleware'; + +export const config = { + matcher: [ + // alles schützen, außer: + // - /api/auth (NextAuth selbst) + // - /login (Login-Seite) + // - /_next (Next.js Assets) + // - /favicon.ico usw. + '/((?!api/auth|login|_next/static|_next/image|favicon.ico).*)', + ], +}; diff --git a/package-lock.json b/package-lock.json index dee014b..70b1bc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,14 @@ "dependencies": { "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", + "@prisma/client": "^6.19.0", + "bcryptjs": "^3.0.3", "next": "16.0.3", + "next-auth": "^4.24.13", "postcss": "^8.5.6", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "ts-node": "^10.9.2" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.17", @@ -233,6 +237,15 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -281,6 +294,28 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -1084,7 +1119,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1094,7 +1128,6 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1313,11 +1346,42 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", + "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/@prisma/config": { "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", @@ -1330,14 +1394,14 @@ "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1351,14 +1415,14 @@ "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.19.0", @@ -1370,7 +1434,7 @@ "version": "6.19.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.19.0" @@ -1484,7 +1548,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@swc/helpers": { @@ -1794,6 +1858,30 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -1830,8 +1918,8 @@ "version": "20.19.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", - "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2432,7 +2520,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "peer": true, "bin": { @@ -2452,6 +2539,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2485,6 +2584,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2732,6 +2837,15 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2795,7 +2909,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.3", @@ -2921,7 +3035,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2937,7 +3051,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "consola": "^3.2.3" @@ -2989,14 +3103,14 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -3009,6 +3123,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3121,7 +3250,7 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" @@ -3167,14 +3296,14 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/detect-libc": { @@ -3187,6 +3316,15 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3204,7 +3342,7 @@ "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3232,7 +3370,7 @@ "version": "3.18.4", "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -3257,7 +3395,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=14" @@ -3907,14 +4045,14 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -4209,7 +4347,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -4907,12 +5045,21 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5357,6 +5504,12 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5514,6 +5667,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.13", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", + "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5546,7 +5731,7 @@ "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-releases": { @@ -5560,7 +5745,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -5576,6 +5761,12 @@ "node": "^14.16.0 || >=16.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5586,6 +5777,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -5703,9 +5903,51 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/oidc-token-hash": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5818,14 +6060,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/picocolors": { @@ -5851,7 +6093,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "confbox": "^0.2.2", @@ -5897,6 +6139,29 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5907,13 +6172,20 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prisma": { "version": "6.19.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "6.19.0", "@prisma/engines": "6.19.0" @@ -5959,7 +6231,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -5997,7 +6269,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", @@ -6038,7 +6310,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -6691,7 +6963,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -6772,6 +7044,49 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -6899,7 +7214,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -6957,7 +7271,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -7045,6 +7358,21 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7167,6 +7495,15 @@ "dev": true, "license": "ISC" }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a646f06..f0d83fc 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,20 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "seed:test-user": "ts-node prisma/create-test-user.ts" }, "dependencies": { "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", + "@prisma/client": "^6.19.0", + "bcryptjs": "^3.0.3", "next": "16.0.3", + "next-auth": "^4.24.13", "postcss": "^8.5.6", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "ts-node": "^10.9.2" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.17", diff --git a/prisma/create-test-user.ts b/prisma/create-test-user.ts new file mode 100644 index 0000000..c041957 --- /dev/null +++ b/prisma/create-test-user.ts @@ -0,0 +1,41 @@ +// prisma/create-test-user.ts +import { PrismaClient } from '@prisma/client'; +import { hash } from 'bcryptjs'; + +const prisma = new PrismaClient(); + +async function main() { + const email = 'christoph.rother@polizei.nrw.de'; + const username = 'admin'; + const password = 'tegvideo7010!'; + + const passwordHash = await hash(password, 10); + + const user = await prisma.user.upsert({ + where: { email }, + update: { + username, + passwordHash, + }, + create: { + email, + username, + name: 'Test User', + passwordHash, + }, + }); + + console.log('Test-User angelegt/aktualisiert:'); + console.log(` Email: ${user.email}`); + console.log(` Username: ${user.username}`); + console.log(` Passwort: ${password}`); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/prisma/dev.db b/prisma/dev.db index da0d2d4eecc37d3249267d83bd5ce22eac0876b1..fa65b2bef4564d04e7ab231191801d10c1390d00 100644 GIT binary patch literal 114688 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 Q009U<00Izzz{?@ Role (Many-to-Many) +model UserRole { + user User @relation(fields: [userId], references: [id]) + userId String + + role Role @relation(fields: [roleId], references: [id]) + roleId String + + assignedAt DateTime @default(now()) + + @@id([userId, roleId]) +} + +/* ────────────────────────────────────────── + Stammdaten: Gruppen & Standorte + ────────────────────────────────────────── */ + +model DeviceGroup { + id String @id @default(cuid()) + name String @unique + devices Device[] +} + +model Location { + id String @id @default(cuid()) + name String // z.B. "Raum 1.12", "Lager Keller" + devices Device[] +} + +/* ────────────────────────────────────────── + Geräte / Inventar + ────────────────────────────────────────── */ + +model Device { + id String @id @default(cuid()) + + // Fachliche Felder + inventoryNumber String @unique // Inventar-Nummer + serialNumber String? // Seriennummer + productNumber String? // Produktnummer + comment String? + + // Beziehungen + group DeviceGroup? @relation(fields: [groupId], references: [id]) + groupId String? + + location Location? @relation(fields: [locationId], references: [id]) + locationId String? + + // Audit-Felder + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + createdBy User? @relation("DeviceCreatedBy", fields: [createdById], references: [id]) + createdById String? + + updatedBy User? @relation("DeviceUpdatedBy", fields: [updatedById], references: [id]) + updatedById String? + + // Historie + history DeviceHistory[] + + @@index([inventoryNumber]) + @@index([groupId]) + @@index([locationId]) +} + +/* ────────────────────────────────────────── + History / Änderungsverlauf + ────────────────────────────────────────── */ + +enum DeviceChangeType { + CREATED + UPDATED + DELETED +} + +model DeviceHistory { + id String @id @default(cuid()) + + device Device @relation(fields: [deviceId], references: [id]) + deviceId String + + changeType DeviceChangeType + + // Snapshot der Gerätedaten zum Zeitpunkt der Änderung + // z.B. { inventoryNumber, serialNumber, productNumber, groupId, locationId, comment, ... } + snapshot Json + + changedAt DateTime @default(now()) + + changedBy User? @relation("DeviceHistoryChangedBy", fields: [changedById], references: [id]) + changedById String? + + @@index([deviceId, changedAt]) +} \ No newline at end of file