DeeCeeXxx's picture
Upload 114 files
e9d5b7d verified
"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'; // Import ObjectId
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; // Limit for accounts per IP
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();
}
// Fallback, might not be available or accurate depending on deployment
// For Firebase App Hosting, 'x-forwarded-for' should be populated by the load balancer.
// Other headers like `fastly-client-ip` or `true-client-ip` might be available too.
return headersList.get('remoteAddress') || null; // 'remoteAddress' is not standard here
}
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.");
// Decide if you want to block registration if IP is not found, or proceed with a warning.
// For now, we'll proceed but log it.
}
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, // Store IP if available
};
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}`);
// For the designated admin email, ensure role is 'admin' and coins are set correctly.
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}`);
// Update the local user object to reflect changes immediately
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') {
// If it's not the ADMIN_EMAIL, then they must already be an admin to use this login route.
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, // 1 week
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;
// registrationIp is not typically exposed in LoggedInUser for privacy, but available in DB User type.
}
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) {
// Ensure admin user has correct role and coins, self-correct if needed
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;
}
}