feat: add authentication system with NextAuth integration

This commit is contained in:
Gergely Hortobágyi
2025-11-28 20:47:24 +01:00
parent 486e584cff
commit 29a0aa3982
14 changed files with 2038 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { LoginForm } from "@/components/auth/login-form"
import Link from "next/link"
export default function LoginPage() {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-brand-50 via-white to-brand-50 p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<Link href="/" className="inline-block mb-6">
<div className="text-3xl font-bold text-brand-600">FABRIKA NABYTOK</div>
</Link>
<h1 className="text-3xl font-bold text-foreground mb-2">Bejelentkezés</h1>
<p className="text-muted-foreground">
Jelentkezzen be fiókjába a folytatáshoz
</p>
<p className="text-sm text-muted-foreground/80 mt-2">
Ügyfelek, munkatársak és adminisztrátorok számára
</p>
</div>
<LoginForm />
<div className="mt-6 space-y-3">
<p className="text-center text-sm text-muted-foreground">
Még nincs fiókja?{" "}
<Link href="/register" className="text-brand-600 hover:underline font-medium">
Regisztráljon itt
</Link>
</p>
<div className="text-center text-xs text-muted-foreground/70 pt-2 border-t border-muted">
<p>A bejelentkezés után automatikusan átirányítjuk Önt a megfelelő felületre.</p>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,27 @@
import { RegisterForm } from "@/components/auth/register-form"
import Link from "next/link"
export default function RegisterPage() {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-brand-50 via-white to-brand-50 p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<Link href="/" className="inline-block mb-6">
<div className="text-3xl font-bold text-brand-600">FABRIKA NABYTOK</div>
</Link>
<h1 className="text-3xl font-bold text-foreground mb-2">Regisztráció</h1>
<p className="text-muted-foreground">Hozzon létre új fiókot a kezdéshez</p>
</div>
<RegisterForm />
<p className="text-center text-sm text-muted-foreground mt-6">
Már van fiókja?{" "}
<Link href="/login" className="text-brand-600 hover:underline font-medium">
Jelentkezzen be itt
</Link>
</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,31 @@
import { isSystemInitialized } from "@/lib/db/setup"
import { redirect } from "next/navigation"
import { SetupWizardForm } from "@/components/auth/setup-wizard-form"
export default async function SetupWizardPage() {
const initialized = await isSystemInitialized()
if (initialized) {
redirect("/")
}
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-brand-50 via-white to-brand-50 p-4">
<div className="w-full max-w-2xl">
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-16 h-16 bg-brand-600 rounded-2xl mb-4">
<svg className="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h1 className="text-4xl font-bold text-foreground mb-2">Üdvözöljük a FABRIKA NABYTOK rendszerben</h1>
<p className="text-lg text-muted-foreground">
A kezdéshez konfigurálja a rendszert és hozza létre a superadmin fiókot
</p>
</div>
<SetupWizardForm />
</div>
</div>
)
}

View File

@@ -0,0 +1,3 @@
import { handlers } from "@/lib/auth/auth"
export const { GET, POST } = handlers

View File

@@ -0,0 +1,119 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Loader2, AlertCircle, CheckCircle2 } from "lucide-react"
import { loginAction } from "@/lib/actions/auth.actions"
export function LoginForm() {
const router = useRouter()
const searchParams = useSearchParams()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const setupSuccess = searchParams.get("setup") === "success"
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setError(null)
setLoading(true)
const formData = new FormData(e.currentTarget)
try {
const result = await loginAction(formData)
if (!result.success) {
setError(result.message)
setLoading(false)
return
}
// Redirect based on callback URL or user role
const callbackUrl = searchParams.get("callbackUrl")
if (callbackUrl) {
router.push(callbackUrl)
} else if (result.redirectTo) {
// Use the role-based redirect from the server
router.push(result.redirectTo)
} else {
// Fallback to home
router.push("/")
}
router.refresh()
} catch (err) {
setError("Hiba történt a bejelentkezés során")
setLoading(false)
}
}
return (
<Card>
<CardHeader>
<CardTitle>Üdvözöljük vissza</CardTitle>
<CardDescription>Adja meg bejelentkezési adatait</CardDescription>
</CardHeader>
<CardContent>
{setupSuccess && (
<div className="flex items-center gap-2 p-3 mb-4 bg-brand-50 border border-brand-200 rounded-lg text-brand-700">
<CheckCircle2 className="w-5 h-5 flex-shrink-0" />
<p className="text-sm">Rendszer sikeresen inicializálva! Jelentkezzen be.</p>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email cím</Label>
<Input
id="email"
name="email"
type="email"
required
placeholder="pelda@email.hu"
disabled={loading}
autoComplete="email"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Jelszó</Label>
<Input
id="password"
name="password"
type="password"
required
placeholder="••••••••"
disabled={loading}
autoComplete="current-password"
/>
</div>
{error && (
<div className="flex items-center gap-2 p-3 bg-destructive/10 border border-destructive/20 rounded-lg text-destructive">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<p className="text-sm">{error}</p>
</div>
)}
<Button type="submit" className="w-full" size="lg" disabled={loading}>
{loading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Bejelentkezés...
</>
) : (
"Bejelentkezés"
)}
</Button>
</form>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,149 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Loader2, AlertCircle } from "lucide-react"
import { registerAction } from "@/lib/actions/auth.actions"
export function RegisterForm() {
const router = useRouter()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setError(null)
setLoading(true)
const formData = new FormData(e.currentTarget)
const password = formData.get("password") as string
const confirmPassword = formData.get("confirmPassword") as string
if (password !== confirmPassword) {
setError("A jelszavak nem egyeznek")
setLoading(false)
return
}
try {
const result = await registerAction(formData)
if (!result.success) {
setError(result.message)
return
}
// Redirect to profile after successful registration
router.push("/profile")
router.refresh()
} catch (err) {
setError("Hiba történt a regisztráció során")
} finally {
setLoading(false)
}
}
return (
<Card>
<CardHeader>
<CardTitle>Új fiók létrehozása</CardTitle>
<CardDescription>Töltse ki az alábbi mezőket a regisztrációhoz</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">Keresztnév</Label>
<Input
id="firstName"
name="firstName"
type="text"
required
placeholder="János"
disabled={loading}
autoComplete="given-name"
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Vezetéknév</Label>
<Input
id="lastName"
name="lastName"
type="text"
required
placeholder="Kovács"
disabled={loading}
autoComplete="family-name"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email cím</Label>
<Input
id="email"
name="email"
type="email"
required
placeholder="pelda@email.hu"
disabled={loading}
autoComplete="email"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Jelszó</Label>
<Input
id="password"
name="password"
type="password"
required
placeholder="Minimum 8 karakter"
disabled={loading}
minLength={8}
autoComplete="new-password"
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Jelszó megerősítése</Label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
required
placeholder="Írja be újra a jelszót"
disabled={loading}
minLength={8}
autoComplete="new-password"
/>
</div>
{error && (
<div className="flex items-center gap-2 p-3 bg-destructive/10 border border-destructive/20 rounded-lg text-destructive">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<p className="text-sm">{error}</p>
</div>
)}
<Button type="submit" className="w-full" size="lg" disabled={loading}>
{loading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Regisztráció...
</>
) : (
"Fiók létrehozása"
)}
</Button>
</form>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,245 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Loader2, CheckCircle2, AlertCircle } from "lucide-react"
export function SetupWizardForm() {
const router = useRouter()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [step, setStep] = useState(1)
const [formData, setFormData] = useState({
email: "",
password: "",
confirmPassword: "",
firstName: "",
lastName: "",
companyName: "",
mongoUri: "",
})
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}))
setError(null)
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
// Validation
if (formData.password !== formData.confirmPassword) {
setError("A jelszavak nem egyeznek")
return
}
if (formData.password.length < 8) {
setError("A jelszónak legalább 8 karakter hosszúnak kell lennie")
return
}
setLoading(true)
try {
const response = await fetch("/api/setup/initialize", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: formData.email,
password: formData.password,
firstName: formData.firstName,
lastName: formData.lastName,
companyName: formData.companyName || undefined,
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || "Hiba történt az inicializálás során")
}
// Success - redirect to login
router.push("/login?setup=success")
} catch (err) {
setError((err as Error).message)
} finally {
setLoading(false)
}
}
return (
<Card className="border-2">
<CardHeader>
<div className="flex items-center justify-between mb-2">
<div className="flex gap-2">
{[1, 2].map((s) => (
<div
key={s}
className={`h-2 w-24 rounded-full transition-colors ${s <= step ? "bg-brand-600" : "bg-muted"}`}
/>
))}
</div>
<span className="text-sm font-medium text-muted-foreground">{step}/2 lépés</span>
</div>
<CardTitle className="text-2xl">
{step === 1 ? "Superadmin fiók létrehozása" : "Vállalati információk"}
</CardTitle>
<CardDescription>
{step === 1
? "Hozza létre a fő adminisztrátori fiókot a rendszer kezeléséhez"
: "Adja meg a vállalat adatait (opcionális)"}
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
{step === 1 ? (
<>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">Keresztnév *</Label>
<Input
id="firstName"
name="firstName"
type="text"
required
value={formData.firstName}
onChange={handleInputChange}
placeholder="János"
disabled={loading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Vezetéknév *</Label>
<Input
id="lastName"
name="lastName"
type="text"
required
value={formData.lastName}
onChange={handleInputChange}
placeholder="Kovács"
disabled={loading}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email cím *</Label>
<Input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleInputChange}
placeholder="admin@pelda.hu"
disabled={loading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Jelszó *</Label>
<Input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleInputChange}
placeholder="Minimum 8 karakter"
disabled={loading}
minLength={8}
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Jelszó megerősítése *</Label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
required
value={formData.confirmPassword}
onChange={handleInputChange}
placeholder="Írja be újra a jelszót"
disabled={loading}
minLength={8}
/>
</div>
<Button
type="button"
onClick={() => setStep(2)}
className="w-full"
size="lg"
disabled={loading || !formData.email || !formData.password || !formData.firstName || !formData.lastName}
>
Tovább
</Button>
</>
) : (
<>
<div className="space-y-2">
<Label htmlFor="companyName">Cég neve</Label>
<Input
id="companyName"
name="companyName"
type="text"
value={formData.companyName}
onChange={handleInputChange}
placeholder="FABRIKA NABYTOK Kft."
disabled={loading}
/>
<p className="text-sm text-muted-foreground">Később módosítható a beállításokban</p>
</div>
{error && (
<div className="flex items-center gap-2 p-3 bg-destructive/10 border border-destructive/20 rounded-lg text-destructive">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<p className="text-sm">{error}</p>
</div>
)}
<div className="flex gap-3">
<Button
type="button"
onClick={() => setStep(1)}
variant="outline"
className="flex-1"
size="lg"
disabled={loading}
>
Vissza
</Button>
<Button type="submit" className="flex-1" size="lg" disabled={loading}>
{loading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Inicializálás...
</>
) : (
<>
<CheckCircle2 className="w-4 h-4 mr-2" />
Rendszer indítása
</>
)}
</Button>
</div>
</>
)}
</form>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,285 @@
# Authentication Flow Diagram
## Login Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ User Login │
│ (All user types use │
│ same /login page) │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Submit Email + Password │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Step 1: Check Users Collection │
│ db.collection("users").findOne({email}) │
└────────────────────────┬────────────────────────────────────────┘
├─────► User not found ──► Error
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: Validate Password │
│ bcrypt.compare(password, user.password) │
└────────────────────────┬────────────────────────────────────────┘
├─────► Invalid ──► Error
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: Check Employee Collection │
│ db.collection("employees").findOne({userId}) │
└────────────────────────┬────────────────────────────────────────┘
┌──────────────┴──────────────┐
│ │
▼ ▼
Employee Found Employee NOT Found
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Update employee │ │ Regular user │
│ lastLogin │ │ (customer, etc.) │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
└──────────────┬──────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: Construct Authenticated User │
│ - Basic user data (id, email, name, role) │
│ - Employee data if applicable: │
│ * isEmployee: true │
│ * employeeId, employeeNumber │
│ * department, position │
│ * permissions │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Step 5: Update Last Login │
│ db.collection("users").updateOne( │
│ {_id}, {$set: {lastLoginAt: new Date()}} │
│ ) │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Step 6: Create JWT Session │
│ - Include all user + employee data │
│ - 7 day expiry │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Step 7: Determine Redirect │
│ getDefaultRedirectForRole(role, isEmployee) │
└────────────────────────┬────────────────────────────────────────┘
┌──────────────┼──────────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Employee │ │ Admin │ │Distribut.│ │ Customer │
│ /internal│ │ /admin │ │/distribu.│ │ /profile │
│/dashboard│ │/dashboard│ │/dashboard│ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
```
## Database Structure
```
┌─────────────────────────────────────────────────────────────────┐
│ USERS Collection │
│ (All authenticated users including employees) │
├─────────────────────────────────────────────────────────────────┤
│ { │
│ _id: ObjectId │
│ email: "user@example.com" │
│ password: "hashed_bcrypt" │
│ firstName: "John" │
│ lastName: "Doe" │
│ role: "warehouse_manager" | "customer" | "admin" | ... │
│ emailVerified: true │
│ lastLoginAt: Date │
│ createdAt: Date │
│ updatedAt: Date │
│ } │
└─────────────────────────────────────────────────────────────────┘
│ Linked by userId
┌─────────────────────────────────────────────────────────────────┐
│ EMPLOYEES Collection │
│ (Additional employee-specific data) │
├─────────────────────────────────────────────────────────────────┤
│ { │
│ _id: ObjectId │
│ userId: "user_id_reference" ◄─── References users._id │
│ employeeNumber: "EMP-001" │
│ role: "warehouse_manager" │
│ department: "Logistics" │
│ position: "Manager" │
│ permissions: ["inventory.view", "orders.pick", ...] │
│ status: "active" | "inactive" | "suspended" │
│ hireDate: Date │
│ lastLogin: Date │
│ metrics: {...} │
│ schedule: {...} │
│ createdAt: Date │
│ updatedAt: Date │
│ } │
└─────────────────────────────────────────────────────────────────┘
```
## Session Structure
```
┌─────────────────────────────────────────────────────────────────┐
│ SESSION │
├─────────────────────────────────────────────────────────────────┤
│ For Regular User (Customer): │
│ { │
│ user: { │
│ id: "user_123" │
│ email: "customer@example.com" │
│ firstName: "Jane" │
│ lastName: "Smith" │
│ role: "customer" │
│ avatar: "https://..." │
│ emailVerified: true │
│ isEmployee: false │
│ } │
│ } │
├─────────────────────────────────────────────────────────────────┤
│ For Employee: │
│ { │
│ user: { │
│ id: "user_456" │
│ email: "employee@example.com" │
│ firstName: "John" │
│ lastName: "Doe" │
│ role: "warehouse_manager" │
│ avatar: "https://..." │
│ emailVerified: true │
│ isEmployee: true │
│ employeeId: "emp_789" │
│ employeeNumber: "EMP-001" │
│ department: "Logistics" │
│ position: "Manager" │
│ permissions: ["inventory.view", "orders.pick", ...] │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────┘
```
## Role-Based Access Control
```
┌─────────────────────────────────────────────────────────────────┐
│ Protected Routes │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┼──────────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
/internal/* /admin/* /distributor/* /profile
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│Check: │ │Check: │ │Check: │ │Check: │
│isEmployee│ │isAdmin │ │role == │ │Any auth │
│== true │ │Role │ │distribut.│ │user │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
Warehouse Dashboard Distributor Personal
Inventory Analytics Portal Profile
Orders Users Orders Settings
Shipping Products Catalogs History
Reports Settings Commission Wishlist
```
## Permission Hierarchy
```
┌───────────────┐
│ SUPERADMIN │
│ (All access) │
└───────┬───────┘
┌───────▼───────┐
│ ADMIN │
│ (Admin panel)│
└───────┬───────┘
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ DISTRIBUTOR│ │ EMPLOYEE │ │ CUSTOMER │
│ (Business) │ │ (Internal) │ │ (Public) │
└────────────┘ └─────┬──────┘ └────────────┘
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Warehouse │ │Inventory │ │ Picker │
│ Manager │ │ Clerk │ │ Packer │
└──────────┘ └──────────┘ └──────────┘
(Full access) (Limited) (Task-based)
```
## Migration Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ OLD STRUCTURE │
│ Employee has separate login credentials │
├─────────────────────────────────────────────────────────────────┤
│ employees collection: │
│ { │
│ email: "employee@example.com" │
│ password: "hashed_password" │
│ firstName: "John" │
│ lastName: "Doe" │
│ ... other employee data ... │
│ } │
└─────────────────────────────────────────────────────────────────┘
│ MIGRATION SCRIPT
┌─────────────────────────────────────────────────────────────────┐
│ NEW STRUCTURE │
├─────────────────────────────────────────────────────────────────┤
│ Step 1: Create user in users collection │
│ { │
│ email: "employee@example.com" │
│ password: "hashed_password" (copied from employee) │
│ firstName: "John" │
│ lastName: "Doe" │
│ role: "warehouse_manager" │
│ } │
│ │ │
│ │ userId = user._id │
│ │ │
│ Step 2: Update employee record │
│ { │
│ userId: "user_id_reference" ◄─── NEW │
│ employeeNumber: "EMP-001" │
│ department: "Logistics" │
│ ... other employee data ... │
│ (password field removed) │
│ } │
└─────────────────────────────────────────────────────────────────┘
```

View File

@@ -0,0 +1,314 @@
# Employee Migration Guide
## Overview
This guide explains how to use the Employee Migration Panel to migrate existing employees from the old authentication system to the new unified authentication system.
## Access
The migration panel is **only accessible to superadmin users** and can be found at:
**Path:** `/admin/users`
The panel appears at the top of the Users page as an orange-highlighted card.
## Migration Process
### Step 1: Preview the Migration
Before running the migration, **always preview** what will happen:
1. Click the **"Előnézet" (Preview)** button
2. Review the information displayed:
- **Total employees to migrate**: Number of employees without `userId`
- **New users to create**: Employees without existing user accounts
- **Link to existing**: Employees with existing user accounts
- **Role updates**: Existing users whose roles will be updated
- **Employee list**: Preview of first 10 employees
#### What the Preview Shows
```
Migráció előnézet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Found X employees to migrate
┌─────────────────────┬──────────────────────┐
│ Migrálásra vár: 15 │ Új felhasználók: 12 │
├─────────────────────┼──────────────────────┤
│ Meglévőkhöz: 3 │ Szerepkör frissítés: 2│
└─────────────────────┴──────────────────────┘
Első 10 munkatárs:
• John Doe (john@example.com) [Új]
• Jane Smith (jane@example.com) [Meglévő] customer → warehouse_manager
• ...
```
### Step 2: Run the Migration
Once you've reviewed the preview and are ready to proceed:
1. Click **"Migráció futtatása" (Run Migration)**
2. A confirmation dialog appears
3. Read the warning carefully
4. Click **"Migráció indítása" (Start Migration)**
5. Wait for the process to complete
The migration will:
- ✅ Create user accounts for employees without existing accounts
- ✅ Link employees to existing user accounts (preserves existing users)
- ✅ Update roles if needed (employee role takes precedence)
- ✅ Remove duplicate passwords from employees collection
- ✅ Update timestamps
### Step 3: Automatic Verification
After successful migration, the system automatically runs a verification check.
The verification displays:
- **Total employees**: Total count in employees collection
- **Missing userId**: Should be 0 after successful migration
- **Invalid userId references**: Should be 0 after successful migration
### Step 4: Manual Verification (Optional)
You can manually verify the migration at any time:
1. Click **"Ellenőrzés" (Verify)**
2. Review the results
## Understanding the Results
### Successful Migration
```
✅ Sikeres migráció
Successfully migrated 15 employees
Migrált munkatársak: 15 / 15
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Ellenőrzés eredménye
Migration verified successfully
Összes munkatárs: 15
Hiányzó userId: 0 ✓
Érvénytelen userId referenciák: 0 ✓
```
### Migration with Errors
```
⚠️ Sikeres migráció
Successfully migrated 13 employees
Migrált munkatársak: 13 / 15
Hibák:
• Failed to migrate employee@example.com: Email already exists
• Failed to migrate test@example.com: Invalid email format
```
If you see errors, check the employee records manually and fix any data issues.
## What Gets Migrated
### Before Migration
**Employees Collection:**
```json
{
"_id": "emp123",
"email": "john@example.com",
"password": "hashed_password", // ← Stored here
"firstName": "John",
"lastName": "Doe",
"role": "warehouse_manager",
"department": "Logistics",
// ... other fields
}
```
**Users Collection:**
```json
// No user exists yet for this employee
```
### After Migration
**Employees Collection:**
```json
{
"_id": "emp123",
"userId": "user456", // ← NEW: Links to user
"email": "john@example.com",
// password field removed
"firstName": "John",
"lastName": "Doe",
"role": "warehouse_manager",
"department": "Logistics",
// ... other fields
}
```
**Users Collection:**
```json
{
"_id": "user456", // ← NEW: User created
"email": "john@example.com",
"password": "hashed_password", // ← Moved here
"firstName": "John",
"lastName": "Doe",
"role": "warehouse_manager",
"emailVerified": true,
"createdAt": "2025-11-26T...",
"updatedAt": "2025-11-26T..."
}
```
## Preserving Existing Users
### Scenario: Employee with Existing User Account
If an employee's email matches an existing user:
**Before:**
- Users: `{email: "john@example.com", role: "customer"}`
- Employees: `{email: "john@example.com", role: "warehouse_manager"}`
**After:**
- Users: `{email: "john@example.com", role: "warehouse_manager"}` ← Role updated
- Employees: `{userId: "user123", email: "john@example.com"}` ← Linked
The existing user account is **preserved** and only the role is updated to match the employee role.
## Safety Features
### 1. Preview Before Migration
Always shows what will happen before making changes.
### 2. Existing User Protection
- Preserves existing user accounts
- Only updates roles when necessary
- Never deletes data
### 3. Verification
Automatic and manual verification ensures data integrity.
### 4. Rollback Capability
For testing purposes, you can rollback the migration.
⚠️ **WARNING**: Rollback should only be used in development/testing!
## Rollback (Testing Only)
If you need to test the migration multiple times:
1. Click **"Visszaállítás" (Rollback)**
2. Confirm the action
3. This removes the `userId` field from all employees
4. You can now run the migration again
⚠️ **IMPORTANT**:
- Rollback does NOT delete created user accounts
- It only removes the `userId` link
- Use with caution in production
## Troubleshooting
### Issue: Preview shows 0 employees
**Cause**: All employees already have `userId` set.
**Solution**: Migration already completed or not needed.
### Issue: Migration fails for some employees
**Possible causes**:
- Invalid email format
- Missing required fields
- Database connection issues
**Solution**:
1. Check error messages
2. Fix data issues in the employees collection
3. Run migration again (already migrated employees are skipped)
### Issue: Verification shows missing userId
**Cause**: Migration was interrupted or failed.
**Solution**: Run the migration again. It will only process employees without `userId`.
### Issue: Invalid userId references
**Cause**: User was deleted but employee still references it.
**Solution**:
1. Find the affected employee(s)
2. Either create the missing user or update the employee's userId
## Post-Migration Checklist
After successful migration:
- [ ] Verification shows 0 missing userId
- [ ] Verification shows 0 invalid references
- [ ] Test employee login on `/login`
- [ ] Verify session contains employee data
- [ ] Check role-based redirects work correctly
- [ ] Test employee-specific features
- [ ] Review migration logs for any errors
## Security Notes
1. **Superadmin Only**: Only superadmin users can access the migration panel
2. **Password Security**: Existing hashed passwords are moved, not re-hashed
3. **Session Integrity**: Existing sessions are not affected
4. **Database Backups**: Always backup before running in production
5. **Audit Trail**: Migration actions are logged
## Production Checklist
Before running migration in production:
1. [ ] **Backup database** completely
2. [ ] Run preview and review results
3. [ ] Test migration in staging environment first
4. [ ] Schedule during low-traffic period
5. [ ] Notify team members
6. [ ] Run migration
7. [ ] Verify results immediately
8. [ ] Test employee logins
9. [ ] Monitor for issues
10. [ ] Document completion
## Support
If you encounter issues:
1. Check the error messages in the migration panel
2. Review the employee and user collections manually
3. Check the server logs for detailed error information
4. Contact technical support with:
- Screenshot of error
- Number of employees to migrate
- Any error messages from verification
## Migration Statistics
The migration panel tracks:
- Total employees processed
- Successfully migrated
- Errors encountered
- New users created
- Existing users linked
- Roles updated
Use this information to ensure complete and accurate migration.

View File

@@ -0,0 +1,263 @@
# Unified Authentication System
## Overview
The authentication system has been refactored to support a unified login experience for all user types:
- **Visitors** (unauthenticated users)
- **Customers** (registered users)
- **Distributors** (business partners)
- **Employees** (internal staff with various roles)
- **Administrators** (admin and superadmin)
## Architecture
### Database Structure
#### Users Collection
All authenticated users (including employees) are stored in the `users` collection:
```typescript
{
_id: ObjectId,
email: string,
password: string (hashed),
firstName: string,
lastName: string,
role: UserRole,
avatar?: string,
emailVerified: boolean,
lastLoginAt?: Date,
createdAt: Date,
updatedAt: Date,
// ... other user fields
}
```
#### Employees Collection
Employee-specific data is stored separately and linked via `userId`:
```typescript
{
_id: ObjectId,
userId: string, // Reference to users._id
employeeNumber: string,
role: EmployeeRole,
department: string,
position: string,
permissions: Permission[],
status: "active" | "inactive" | "suspended",
lastLogin?: Date,
// ... other employee fields
}
```
### Authentication Flow
1. **User Login**
- User submits email and password
- System checks `users` collection for authentication
- Password is validated using bcrypt
2. **Employee Check**
- After successful authentication, system checks `employees` collection
- Searches for employee record with matching `userId`
- If found, employee data is merged into the session
3. **Session Creation**
- JWT token is created with user data
- Employee-specific fields are included if applicable:
- `isEmployee`: boolean
- `employeeId`: string
- `employeeNumber`: string
- `department`: string
- `position`: string
- `permissions`: Permission[]
4. **Role-Based Redirect**
- System determines appropriate redirect based on:
- User role
- Employee status
- Callback URL (if provided)
### Redirect Logic
The `getDefaultRedirectForRole()` function determines where users are sent after login:
| User Type | Role | Redirect Path |
|-----------|------|---------------|
| Employee | warehouse_manager, inventory_clerk, etc. | `/internal/dashboard` |
| Admin | admin, superadmin | `/admin/dashboard` |
| Distributor | distributor | `/distributor/dashboard` |
| Customer | customer | `/profile` |
| Visitor | visitor | `/` |
## Key Files
### Auth Configuration
- **`lib/auth/auth.ts`** - NextAuth configuration and credentials provider
- **`lib/auth/auth.config.ts`** - Base auth configuration
- **`lib/auth/auth-helpers.ts`** - Role-based utilities and redirect logic
### Types
- **`lib/types/user.types.ts`** - User and role type definitions
- **`lib/types/employee.types.ts`** - Employee-specific types
- **`lib/types/next-auth.d.ts`** - NextAuth type extensions
### Actions
- **`lib/actions/auth.actions.ts`** - Server actions for login/logout/register
### Components
- **`components/auth/login-form.tsx`** - Login form component
- **`app/(auth)/login/page.tsx`** - Login page
## Usage Examples
### Creating an Employee
To create a new employee:
1. First, create a user account in the `users` collection:
```typescript
const user = {
email: "employee@example.com",
password: await hash("password", 12),
firstName: "John",
lastName: "Doe",
role: "warehouse_manager", // Employee role
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
}
const result = await db.collection("users").insertOne(user)
```
2. Then, create the employee record linked to the user:
```typescript
const employee = {
userId: result.insertedId.toString(),
employeeNumber: "EMP-001",
role: "warehouse_manager",
department: "Logistics",
position: "Manager",
permissions: [...], // Role-based permissions
status: "active",
hireDate: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
}
await db.collection("employees").insertOne(employee)
```
### Accessing Session Data
In server components:
```typescript
import { auth } from "@/lib/auth/auth"
const session = await auth()
if (session?.user) {
const { role, isEmployee, employeeId, permissions } = session.user
if (isEmployee) {
// Employee-specific logic
}
}
```
In client components:
```typescript
import { useSession } from "next-auth/react"
const { data: session } = useSession()
if (session?.user.isEmployee) {
// Employee-specific UI
}
```
### Checking Permissions
```typescript
import { canAccessAdmin, canAccessInternal } from "@/lib/auth/auth-helpers"
const session = await auth()
if (canAccessAdmin(session.user.role)) {
// Admin panel access
}
if (canAccessInternal(session.user.role, session.user.isEmployee)) {
// Internal employee portal access
}
```
## Security Considerations
1. **Password Storage**: All passwords are hashed using bcrypt with 12 rounds
2. **Session Management**: JWT-based sessions with 7-day expiry
3. **Role Validation**: Role and permission checks are performed server-side
4. **Database Queries**: Protected against injection via MongoDB driver
5. **Employee Verification**: Two-step verification (user + employee record)
## Migration Guide
### Migrating Existing Employees
If you have employees with separate login credentials:
1. **Backup existing data**
2. **For each employee:**
```typescript
// Create user account
const user = await db.collection("users").insertOne({
email: employee.email,
password: employee.password, // Already hashed
firstName: employee.firstName,
lastName: employee.lastName,
role: employee.role,
emailVerified: true,
createdAt: employee.createdAt,
updatedAt: new Date(),
})
// Update employee record
await db.collection("employees").updateOne(
{ _id: employee._id },
{
$set: {
userId: user.insertedId.toString(),
updatedAt: new Date()
}
}
)
```
## Troubleshooting
### Issue: "Hibás email vagy jelszó"
- Verify user exists in `users` collection
- Check password hash matches
- Ensure user is not suspended/inactive
### Issue: Employee not recognized
- Verify `employees` collection has record with matching `userId`
- Check that `userId` matches the `_id` from users collection
- Ensure employee status is "active"
### Issue: Wrong redirect after login
- Check user role in database
- Verify `isEmployee` flag in session
- Review `getDefaultRedirectForRole()` logic
## Future Enhancements
- [ ] Two-factor authentication (2FA)
- [ ] OAuth providers (Google, Microsoft)
- [ ] Remember me functionality
- [ ] Login attempt rate limiting
- [ ] Account lockout after failed attempts
- [ ] Password reset flow
- [ ] Email verification flow
- [ ] Session management dashboard

View File

@@ -0,0 +1,135 @@
/**
* Authentication Helper Utilities
* Role-based redirects and user type detection
*/
import type { UserRole } from "@/lib/types/user.types"
/**
* Get default redirect path based on user role
*/
export function getDefaultRedirectForRole(role: UserRole, isEmployee?: boolean): string {
// Employee roles
if (isEmployee) {
switch (role) {
case "warehouse_manager":
case "inventory_clerk":
case "picker":
case "packer":
case "shipper":
case "location_manager":
case "outsource_coordinator":
case "quality_controller":
case "assistant":
return "/internal/dashboard"
default:
break
}
}
// Admin and superadmin
if (role === "admin" || role === "superadmin") {
return "/admin/dashboard"
}
// Distributor
if (role === "distributor") {
return "/distributor/dashboard"
}
// Customer
if (role === "customer") {
return "/profile"
}
// Visitor (default)
return "/"
}
/**
* Check if user role is an employee role
*/
export function isEmployeeRole(role: UserRole): boolean {
const employeeRoles: UserRole[] = [
"warehouse_manager",
"inventory_clerk",
"picker",
"packer",
"shipper",
"location_manager",
"outsource_coordinator",
"quality_controller",
"assistant",
]
return employeeRoles.includes(role)
}
/**
* Check if user role is an admin role
*/
export function isAdminRole(role: UserRole): boolean {
return role === "admin" || role === "superadmin"
}
/**
* Check if user has access to admin panel
*/
export function canAccessAdmin(role: UserRole): boolean {
return isAdminRole(role)
}
/**
* Check if user has access to internal employee portal
*/
export function canAccessInternal(role: UserRole, isEmployee?: boolean): boolean {
return isEmployee === true || isEmployeeRole(role)
}
/**
* Get user type label for display
*/
export function getUserTypeLabel(role: UserRole, isEmployee?: boolean): string {
if (isEmployee || isEmployeeRole(role)) {
return "Munkatárs"
}
switch (role) {
case "superadmin":
return "Szuper adminisztrátor"
case "admin":
return "Adminisztrátor"
case "distributor":
return "Viszonteladó"
case "customer":
return "Ügyfél"
case "visitor":
return "Látogató"
default:
return "Felhasználó"
}
}
/**
* Get role display name in Hungarian
*/
export function getRoleDisplayName(role: UserRole): string {
const roleNames: Record<UserRole, string> = {
visitor: "Látogató",
customer: "Ügyfél",
distributor: "Viszonteladó",
admin: "Adminisztrátor",
superadmin: "Szuper adminisztrátor",
warehouse_manager: "Raktárvezető",
inventory_clerk: "Készletkezelő",
picker: "Komissiózó",
packer: "Csomagoló",
shipper: "Szállítmányozó",
location_manager: "Helyszín menedzser",
outsource_coordinator: "Beszerzési koordinátor",
quality_controller: "Minőségellenőr",
assistant: "Asszisztens",
}
return roleNames[role] || role
}

View File

@@ -0,0 +1,8 @@
import type { NextAuthConfig } from "next-auth"
import Credentials from "next-auth/providers/credentials"
export default {
providers: [
Credentials
]
} satisfies NextAuthConfig

View File

@@ -0,0 +1,162 @@
import NextAuth from "next-auth"
import authConfig from "./auth.config"
import { MongoClient } from "mongodb"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import Credentials from "next-auth/providers/credentials"
import { getDb, getClient } from "@/lib/db/mongodb"
import bcrypt from "bcryptjs"
import type { UserRole } from "@/lib/types/user.types"
import { ObjectId } from "mongodb"
import { Permission } from "../types/employee.types"
const client = await getClient()
const db = await getDb()
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
adapter: MongoDBAdapter(client as unknown as MongoClient) as any,
session: {
strategy: "jwt",
maxAge: 7 * 24 * 60 * 60, // 7 days
},
pages: {
signIn: "/login",
error: "/login",
},
providers: [
Credentials({
id: "credentials",
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
// @ts-ignore
async authorize(credentials) {
if (!db) {
throw new Error("Database not connected")
}
if (!credentials?.email || !credentials?.password) {
throw new Error("Hibás email vagy jelszó")
}
// Step 1: Authenticate against users collection
const user = await db
.collection("users")
.findOne({ email: credentials.email })
if (!user) {
throw new Error("Hibás email vagy jelszó")
}
// Step 2: Verify password
const isValidPassword = await bcrypt.compare(
credentials.password as string,
user.password
)
if (!isValidPassword) {
throw new Error("Hibás email vagy jelszó")
}
// Step 3: Check if user is an employee
const employee = await db
.collection("employees")
.findOne({ userId: user._id.toString() })
// Step 4: Update last login timestamp
await db.collection("users").updateOne(
{ _id: new ObjectId(user._id) },
{
$set: {
lastLoginAt: new Date(),
updatedAt: new Date()
}
}
)
// Step 5: If employee exists, update their last login
if (employee) {
await db.collection("employees").updateOne(
{ _id: new ObjectId(employee._id) },
{
$set: {
lastLogin: new Date(),
updatedAt: new Date()
}
}
)
}
// Step 6: Construct the authenticated user object
const authenticatedUser = {
id: user._id.toString(),
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
name: `${user.firstName || ''} ${user.lastName || ''}`.trim() || user.email,
role: employee ? employee.role : user.role,
avatar: user.avatar,
emailVerified: user.emailVerified,
// Include employee-specific data if applicable
employeeId: employee?._id?.toString(),
employeeNumber: employee?.employeeNumber,
department: employee?.department,
position: employee?.position,
permissions: employee?.permissions || [],
isEmployee: !!employee,
}
return authenticatedUser
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.role = user.role
token.firstName = user.firstName
token.lastName = user.lastName
token.avatar = user.avatar
token.emailVerified = user.emailVerified as boolean | Date | undefined
// Employee-specific fields
token.isEmployee = user.isEmployee
token.employeeId = user.employeeId
token.employeeNumber = user.employeeNumber
token.department = user.department
token.position = user.position
token.permissions = user.permissions
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string
session.user.role = token.role as UserRole
session.user.firstName = token.firstName as string
session.user.lastName = token.lastName as string
session.user.avatar = token.avatar as string
session.user.emailVerified = token.emailVerified as Date & boolean
// Employee-specific fields
session.user.isEmployee = token.isEmployee as boolean
session.user.employeeId = token.employeeId as string
session.user.employeeNumber = token.employeeNumber as string
session.user.department = token.department as string
session.user.position = token.position as string
session.user.permissions = token.permissions as Permission[]
}
return session
},
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
if (url.startsWith("/")) return `${baseUrl}${url}`
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url
return baseUrl
},
},
trustHost: true,
})

View File

@@ -0,0 +1,259 @@
/**
* Employee Migration Script
* Migrates employees from separate authentication to unified user system
*/
import { getDb } from "@/lib/db/mongodb"
import { ObjectId } from "mongodb"
interface OldEmployee {
_id: ObjectId
email: string
password: string // Already hashed
firstName: string
lastName: string
role: string
department: string
position: string
permissions: string[]
status: string
hireDate: Date
createdAt: Date
[key: string]: any
}
/**
* Migrate employees to the new unified authentication system
*
* This script:
* 1. Finds all employees without a userId
* 2. Creates corresponding user accounts
* 3. Links employees to users via userId
*/
export async function migrateEmployeesToUnifiedAuth() {
const db = await getDb()
if (!db) {
throw new Error("Database not connected")
}
console.log("🔄 Starting employee migration...")
try {
// Find employees without userId (old structure)
const employeesWithoutUser = await db
.collection("employees")
.find({
$or: [
{ userId: { $exists: false } },
{ userId: null },
{ userId: "" }
]
})
.toArray() as unknown as OldEmployee[]
console.log(`📊 Found ${employeesWithoutUser.length} employees to migrate`)
if (employeesWithoutUser.length === 0) {
console.log("✅ No employees to migrate")
return {
success: true,
migrated: 0,
errors: [],
}
}
const errors: string[] = []
let migratedCount = 0
for (const employee of employeesWithoutUser) {
try {
console.log(`\n👤 Processing: ${employee.firstName} ${employee.lastName} (${employee.email})`)
// Check if user already exists with this email
const existingUser = await db
.collection("users")
.findOne({ email: employee.email })
let userId: string
if (existingUser) {
console.log(` User already exists (${existingUser.role}), linking to employee...`)
userId = existingUser._id.toString()
// If the existing user has a different role, update it to the employee role
// This ensures employees have the correct role in the users collection
if (existingUser.role !== employee.role) {
console.log(` 🔄 Updating user role from ${existingUser.role} to ${employee.role}`)
await db.collection("users").updateOne(
{ _id: existingUser._id },
{
$set: {
role: employee.role,
updatedAt: new Date()
}
}
)
}
} else {
// Create new user account
console.log(` Creating new user account...`)
const newUser = {
email: employee.email,
password: employee.password, // Use existing hashed password
firstName: employee.firstName,
lastName: employee.lastName,
role: employee.role,
avatar: employee.avatar || null,
phone: employee.phone || null,
emailVerified: true, // Employees are pre-verified
isActive: employee.status === "active",
creditBalance: 0,
createdAt: employee.createdAt || new Date(),
updatedAt: new Date(),
lastLoginAt: employee.lastLogin || null,
}
const userResult = await db.collection("users").insertOne(newUser)
userId = userResult.insertedId.toString()
console.log(` ✅ User created with ID: ${userId}`)
}
// Update employee record with userId
await db.collection("employees").updateOne(
{ _id: employee._id },
{
$set: {
userId: userId,
updatedAt: new Date()
},
// Remove password from employee record (now stored in users)
$unset: { password: "" }
}
)
console.log(` ✅ Employee linked to user`)
migratedCount++
} catch (error) {
const errorMsg = `Failed to migrate ${employee.email}: ${error}`
console.error(`${errorMsg}`)
errors.push(errorMsg)
}
}
console.log(`\n${"=".repeat(50)}`)
console.log(`✅ Migration complete!`)
console.log(` Migrated: ${migratedCount}/${employeesWithoutUser.length}`)
if (errors.length > 0) {
console.log(` Errors: ${errors.length}`)
console.log(`\n❌ Errors:`)
errors.forEach(err => console.log(` - ${err}`))
}
return {
success: true,
migrated: migratedCount,
total: employeesWithoutUser.length,
errors,
}
} catch (error) {
console.error("❌ Migration failed:", error)
throw error
}
}
/**
* Verify migration was successful
* Checks that all employees have valid userId references
*/
export async function verifyEmployeeMigration() {
const db = await getDb()
if (!db) {
throw new Error("Database not connected")
}
console.log("🔍 Verifying employee migration...")
// Check for employees without userId
const employeesWithoutUser = await db
.collection("employees")
.countDocuments({
$or: [
{ userId: { $exists: false } },
{ userId: null },
{ userId: "" }
]
})
// Check for employees with invalid userId
const employees = await db.collection("employees").find({}).toArray()
let invalidUserIds = 0
for (const employee of employees) {
if (employee.userId) {
const user = await db
.collection("users")
.findOne({ _id: new ObjectId(employee.userId) })
if (!user) {
console.log(`❌ Employee ${employee.email} has invalid userId: ${employee.userId}`)
invalidUserIds++
}
}
}
console.log(`\n📊 Verification Results:`)
console.log(` Total employees: ${employees.length}`)
console.log(` Without userId: ${employeesWithoutUser}`)
console.log(` Invalid userId references: ${invalidUserIds}`)
const isValid = employeesWithoutUser === 0 && invalidUserIds === 0
if (isValid) {
console.log(`\n✅ Migration verified successfully!`)
} else {
console.log(`\n❌ Migration has issues that need to be resolved`)
}
return {
success: isValid,
totalEmployees: employees.length,
missingUserId: employeesWithoutUser,
invalidUserIdReferences: invalidUserIds,
}
}
/**
* Rollback migration (for testing purposes)
* WARNING: This will remove the userId field from employees
*/
export async function rollbackEmployeeMigration() {
const db = await getDb()
if (!db) {
throw new Error("Database not connected")
}
console.log("⚠️ WARNING: Rolling back employee migration...")
console.log("This will remove userId from all employee records")
const result = await db.collection("employees").updateMany(
{},
{
$unset: { userId: "" }
}
)
console.log(`✅ Rolled back ${result.modifiedCount} employee records`)
return {
success: true,
modified: result.modifiedCount,
}
}