|
|
|
|
|
"use server"; |
|
|
|
|
|
import type { LoginInput, RegisterInput, AdminLoginInput } from "@/lib/schemas"; |
|
|
import { getDb } from "@/lib/mongodb"; |
|
|
import bcrypt from 'bcryptjs'; |
|
|
import { cookies, headers as nextHeaders } from 'next/headers'; |
|
|
import { redirect } from 'next/navigation'; |
|
|
import type { User } from "@/lib/types"; |
|
|
import { revalidatePath } from "next/cache"; |
|
|
import { ObjectId } from 'mongodb'; |
|
|
|
|
|
const ADMIN_EMAIL = 'ogdavidcyril@gmail.com'; |
|
|
const REFERRAL_COIN_BONUS_FOR_REFERRER = 10; |
|
|
const REFERRAL_WELCOME_BONUS_FOR_NEW_USER = 5; |
|
|
const ADMIN_INITIAL_COINS = 999999; |
|
|
const MAX_ACCOUNTS_PER_IP = 1; |
|
|
|
|
|
|
|
|
export async function loginUser(data: LoginInput) { |
|
|
try { |
|
|
const db = await getDb(); |
|
|
const usersCollection = db.collection<User>("users"); |
|
|
|
|
|
const user = await usersCollection.findOne({ email: data.email }); |
|
|
|
|
|
if (!user) { |
|
|
return { success: false, message: "Invalid email or password." }; |
|
|
} |
|
|
|
|
|
const isPasswordValid = await bcrypt.compare(data.password, user.passwordHash); |
|
|
|
|
|
if (!isPasswordValid) { |
|
|
return { success: false, message: "Invalid email or password." }; |
|
|
} |
|
|
|
|
|
cookies().set('loggedInUserId', user._id.toString(), { |
|
|
path: '/', |
|
|
httpOnly: true, |
|
|
secure: process.env.NODE_ENV === 'production', |
|
|
maxAge: 60 * 60 * 24 * 7, |
|
|
sameSite: 'lax', |
|
|
}); |
|
|
redirect('/dashboard'); |
|
|
|
|
|
} catch (error) { |
|
|
if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('NEXT_REDIRECT')) { |
|
|
throw error; |
|
|
} |
|
|
console.error("Login error:", error); |
|
|
return { success: false, message: "An unexpected error occurred during login." }; |
|
|
} |
|
|
} |
|
|
|
|
|
function getClientIp(): string | null { |
|
|
const headersList = nextHeaders(); |
|
|
const xForwardedFor = headersList.get('x-forwarded-for'); |
|
|
if (xForwardedFor) { |
|
|
return xForwardedFor.split(',')[0].trim(); |
|
|
} |
|
|
const xRealIp = headersList.get('x-real-ip'); |
|
|
if (xRealIp) { |
|
|
return xRealIp.trim(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return headersList.get('remoteAddress') || null; |
|
|
} |
|
|
|
|
|
|
|
|
export async function registerUser(data: RegisterInput) { |
|
|
try { |
|
|
const db = await getDb(); |
|
|
const usersCollection = db.collection<User>("users"); |
|
|
|
|
|
const existingUser = await usersCollection.findOne({ email: data.email }); |
|
|
if (existingUser) { |
|
|
return { success: false, message: "This email is already registered." }; |
|
|
} |
|
|
|
|
|
const clientIp = getClientIp(); |
|
|
if (clientIp) { |
|
|
const existingAccountsWithIp = await usersCollection.countDocuments({ registrationIp: clientIp }); |
|
|
if (existingAccountsWithIp >= MAX_ACCOUNTS_PER_IP) { |
|
|
return { success: false, message: "Registration limit reached for this network. Please try again later or contact support if you believe this is an error." }; |
|
|
} |
|
|
} else { |
|
|
console.warn("Could not determine client IP address for registration. IP-based limit will not be applied for this user."); |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
const passwordHash = await bcrypt.hash(data.password, 10); |
|
|
|
|
|
let roleToAssign: 'user' | 'admin' = 'user'; |
|
|
let initialCoins = 0; |
|
|
|
|
|
if (data.email === ADMIN_EMAIL) { |
|
|
roleToAssign = 'admin'; |
|
|
initialCoins = ADMIN_INITIAL_COINS; |
|
|
} |
|
|
|
|
|
const newUserObjectIdString = new ObjectId().toString(); |
|
|
|
|
|
const namePart = data.name.substring(0, 5).toLowerCase().replace(/[^a-z0-9]/g, ''); |
|
|
const randomPart = newUserObjectIdString.slice(-6); |
|
|
const referralCode = `${namePart}${randomPart}`; |
|
|
|
|
|
const newUserDocument: User = { |
|
|
_id: newUserObjectIdString, |
|
|
name: data.name, |
|
|
email: data.email, |
|
|
passwordHash, |
|
|
role: roleToAssign, |
|
|
coins: initialCoins, |
|
|
lastCoinClaim: null, |
|
|
referralCode: referralCode, |
|
|
referredBy: null, |
|
|
createdAt: new Date(), |
|
|
registrationIp: clientIp || undefined, |
|
|
}; |
|
|
|
|
|
if (data.referralCode) { |
|
|
const referrer = await usersCollection.findOne({ referralCode: data.referralCode }); |
|
|
if (referrer) { |
|
|
await usersCollection.updateOne( |
|
|
{ _id: referrer._id }, |
|
|
{ $inc: { coins: REFERRAL_COIN_BONUS_FOR_REFERRER } } |
|
|
); |
|
|
newUserDocument.referredBy = referrer._id.toString(); |
|
|
if (data.email !== ADMIN_EMAIL) { |
|
|
newUserDocument.coins += REFERRAL_WELCOME_BONUS_FOR_NEW_USER; |
|
|
} |
|
|
} else { |
|
|
console.log(`Referral code ${data.referralCode} not found or invalid.`); |
|
|
} |
|
|
} |
|
|
|
|
|
const result = await usersCollection.insertOne(newUserDocument); |
|
|
|
|
|
if (!result.insertedId) { |
|
|
return { success: false, message: "Failed to register user." }; |
|
|
} |
|
|
|
|
|
return { success: true, message: "Registration successful! You can now log in." }; |
|
|
} catch (error) { |
|
|
console.error("Registration error:", error); |
|
|
return { success: false, message: "An unexpected error occurred during registration." }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export async function adminLoginUser(data: AdminLoginInput) { |
|
|
try { |
|
|
const db = await getDb(); |
|
|
const usersCollection = db.collection<User>("users"); |
|
|
|
|
|
const user = await usersCollection.findOne({ email: data.email }); |
|
|
|
|
|
if (!user) { |
|
|
console.log(`Admin login attempt: User not found for email ${data.email}`); |
|
|
return { success: false, message: "Invalid email or password." }; |
|
|
} |
|
|
|
|
|
const isPasswordValid = await bcrypt.compare(data.password, user.passwordHash); |
|
|
|
|
|
if (!isPasswordValid) { |
|
|
console.log(`Admin login attempt: Invalid password for email ${data.email}`); |
|
|
return { success: false, message: "Invalid email or password." }; |
|
|
} |
|
|
console.log(`Admin login attempt: Password valid for ${data.email}`); |
|
|
|
|
|
|
|
|
if (user.email === ADMIN_EMAIL) { |
|
|
const updates: Partial<Pick<User, 'role' | 'coins'>> = {}; |
|
|
let needsDatabaseUpdate = false; |
|
|
|
|
|
if (user.role !== 'admin') { |
|
|
updates.role = 'admin'; |
|
|
needsDatabaseUpdate = true; |
|
|
console.log(`Admin login: ${user.email} role is ${user.role}, needs update to admin.`); |
|
|
} |
|
|
if (user.coins !== ADMIN_INITIAL_COINS) { |
|
|
updates.coins = ADMIN_INITIAL_COINS; |
|
|
needsDatabaseUpdate = true; |
|
|
console.log(`Admin login: ${user.email} coins are ${user.coins}, needs update to ${ADMIN_INITIAL_COINS}.`); |
|
|
} |
|
|
|
|
|
if (needsDatabaseUpdate) { |
|
|
await usersCollection.updateOne({ _id: user._id }, { $set: updates }); |
|
|
console.log(`Admin user ${user.email} DB record updated/ensured: role to ${updates.role || user.role}, coins to ${updates.coins !== undefined ? updates.coins : user.coins}`); |
|
|
|
|
|
if (updates.role) user.role = updates.role; |
|
|
if (updates.coins !== undefined) user.coins = updates.coins; |
|
|
} else { |
|
|
console.log(`Admin login: ${user.email} already has correct admin role and coins.`); |
|
|
} |
|
|
} else if (user.role !== 'admin') { |
|
|
|
|
|
console.log(`Admin login attempt: ${data.email} is not the ADMIN_EMAIL and does not have admin role.`); |
|
|
return { success: false, message: "Access denied. Not an admin account." }; |
|
|
} |
|
|
|
|
|
console.log(`Admin login: Setting cookie for user ${user._id} (${user.email}) with role ${user.role}`); |
|
|
cookies().set('loggedInUserId', user._id.toString(), { |
|
|
path: '/', |
|
|
httpOnly: true, |
|
|
secure: process.env.NODE_ENV === 'production', |
|
|
maxAge: 60 * 60 * 24 * 7, |
|
|
sameSite: 'lax', |
|
|
}); |
|
|
|
|
|
revalidatePath('/admin/dashboard'); |
|
|
console.log(`Admin login: Revalidated /admin/dashboard, redirecting...`); |
|
|
redirect('/admin/dashboard'); |
|
|
|
|
|
} catch (error) { |
|
|
if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('NEXT_REDIRECT')) { |
|
|
throw error; |
|
|
} |
|
|
console.error("Admin login error:", error); |
|
|
return { success: false, message: "An unexpected error occurred during admin login." }; |
|
|
} |
|
|
} |
|
|
|
|
|
export async function logoutUser() { |
|
|
try { |
|
|
cookies().delete('loggedInUserId'); |
|
|
revalidatePath('/'); |
|
|
redirect('/login'); |
|
|
} catch (error) { |
|
|
if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('NEXT_REDIRECT')) { |
|
|
throw error; |
|
|
} |
|
|
console.error("Logout error:", error); |
|
|
return { success: false, message: "Logout failed due to an unexpected error." }; |
|
|
} |
|
|
} |
|
|
|
|
|
export interface LoggedInUser { |
|
|
_id: string; |
|
|
name: string; |
|
|
email: string; |
|
|
role: 'user' | 'admin'; |
|
|
coins: number; |
|
|
lastCoinClaim: Date | null; |
|
|
referralCode: string; |
|
|
createdAt: Date; |
|
|
referredUsersCount: number; |
|
|
referralCoinsEarned: number; |
|
|
|
|
|
} |
|
|
|
|
|
export async function getLoggedInUser(): Promise<LoggedInUser | null> { |
|
|
try { |
|
|
const userIdCookie = cookies().get('loggedInUserId'); |
|
|
if (!userIdCookie?.value) { |
|
|
return null; |
|
|
} |
|
|
const userIdString = userIdCookie.value; |
|
|
|
|
|
const db = await getDb(); |
|
|
const usersCollection = db.collection<User>('users'); |
|
|
const user = await usersCollection.findOne({ _id: userIdString }); |
|
|
|
|
|
if (user) { |
|
|
|
|
|
if (user.email === ADMIN_EMAIL) { |
|
|
let adminNeedsUpdate = false; |
|
|
const adminUpdates: Partial<Pick<User, 'role' | 'coins'>> = {}; |
|
|
if (user.role !== 'admin') { |
|
|
adminUpdates.role = 'admin'; |
|
|
adminNeedsUpdate = true; |
|
|
} |
|
|
if (user.coins !== ADMIN_INITIAL_COINS) { |
|
|
adminUpdates.coins = ADMIN_INITIAL_COINS; |
|
|
adminNeedsUpdate = true; |
|
|
} |
|
|
if (adminNeedsUpdate) { |
|
|
await usersCollection.updateOne({ _id: user._id }, { $set: adminUpdates }); |
|
|
if(adminUpdates.role) user.role = adminUpdates.role; |
|
|
if(adminUpdates.coins !== undefined ) user.coins = adminUpdates.coins; |
|
|
console.log(`Self-correction in getLoggedInUser for ${user.email}: role to ${user.role}, coins to ${user.coins}`); |
|
|
} |
|
|
} |
|
|
|
|
|
const referredUsersCount = await usersCollection.countDocuments({ referredBy: user._id }); |
|
|
const referralCoinsEarned = referredUsersCount * REFERRAL_COIN_BONUS_FOR_REFERRER; |
|
|
|
|
|
return { |
|
|
_id: user._id, |
|
|
name: user.name, |
|
|
email: user.email, |
|
|
role: user.role || 'user', |
|
|
coins: user.coins || 0, |
|
|
lastCoinClaim: user.lastCoinClaim ? new Date(user.lastCoinClaim) : null, |
|
|
referralCode: user.referralCode || 'N/A', |
|
|
createdAt: user.createdAt ? new Date(user.createdAt) : new Date(0), |
|
|
referredUsersCount: referredUsersCount, |
|
|
referralCoinsEarned: referralCoinsEarned, |
|
|
}; |
|
|
} |
|
|
console.log(`getLoggedInUser: No user found for ID ${userIdString}`); |
|
|
return null; |
|
|
} catch (error) { |
|
|
console.error("Error fetching logged in user:", error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|