File size: 9,342 Bytes
e9d5b7d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
"use server";
import { getDb } from "@/lib/mongodb";
import { getLoggedInUser, logoutUser } from "@/lib/actions/auth";
import bcrypt from 'bcryptjs';
import { differenceInHours } from 'date-fns';
import type { User, Deployment } from "@/lib/types";
import type { TransferCoinsInput, ChangePasswordInput, UpdateProfileInput, DeleteAccountInput } from "@/lib/schemas";
import { revalidatePath } from 'next/cache';
const DAILY_COIN_AMOUNT = 10;
const CLAIM_COOLDOWN_HOURS = 24;
export async function claimDailyCoins(): Promise<{ success: boolean; message: string; newBalance?: number; nextClaimAvailableInMs?: number }> {
const user = await getLoggedInUser();
if (!user) {
return { success: false, message: "You must be logged in to claim coins." };
}
const now = new Date();
if (user.lastCoinClaim) {
const hoursSinceLastClaim = differenceInHours(now, new Date(user.lastCoinClaim));
if (hoursSinceLastClaim < CLAIM_COOLDOWN_HOURS) {
const nextClaimTime = new Date(user.lastCoinClaim).getTime() + CLAIM_COOLDOWN_HOURS * 60 * 60 * 1000;
const nextClaimAvailableInMs = Math.max(0, nextClaimTime - now.getTime());
return {
success: false,
message: `You can claim again in ${CLAIM_COOLDOWN_HOURS - hoursSinceLastClaim} hour(s).`,
nextClaimAvailableInMs
};
}
}
try {
const db = await getDb();
const usersCollection = db.collection<User>("users");
// Query by string _id directly
const updatedUserResult = await usersCollection.findOneAndUpdate(
{ _id: user._id },
{
$inc: { coins: DAILY_COIN_AMOUNT },
$set: { lastCoinClaim: now }
},
{ returnDocument: "after" }
);
if (!updatedUserResult) {
return { success: false, message: "Failed to update user balance. User not found or update failed." };
}
revalidatePath("/dashboard");
return {
success: true,
message: `Successfully claimed ${DAILY_COIN_AMOUNT} coins!`,
newBalance: updatedUserResult.coins,
nextClaimAvailableInMs: CLAIM_COOLDOWN_HOURS * 60 * 60 * 1000
};
} catch (error) {
console.error("Error claiming daily coins:", error);
return { success: false, message: "An unexpected error occurred." };
}
}
export async function transferCoins(data: TransferCoinsInput): Promise<{ success: boolean; message: string; newSenderBalance?: number }> {
const sender = await getLoggedInUser();
if (!sender) {
return { success: false, message: "You must be logged in to transfer coins." };
}
if (data.amount <= 0) {
return { success: false, message: "Transfer amount must be positive." };
}
if (sender.coins < data.amount && sender.role !== 'admin') {
return { success: false, message: "Insufficient coin balance." };
}
if (sender.email === data.recipientEmail) {
return { success: false, message: "You cannot transfer coins to yourself." };
}
try {
const db = await getDb();
const usersCollection = db.collection<User>("users");
const recipient = await usersCollection.findOne({ email: data.recipientEmail });
if (!recipient) {
return { success: false, message: "Recipient user not found." };
}
if (sender.role !== 'admin') {
// Query by string _id directly
const freshSenderData = await usersCollection.findOne({ _id: sender._id });
if (!freshSenderData || freshSenderData.coins < data.amount) {
return { success: false, message: "Insufficient coin balance. Please refresh and try again." };
}
}
const senderUpdateResult = await usersCollection.updateOne(
{ _id: sender._id }, // Query by string _id
{ $inc: { coins: -data.amount } }
);
if (senderUpdateResult.modifiedCount === 0) {
if (sender.role !== 'admin') {
return { success: false, message: "Failed to update your balance. Please try again." };
}
}
const recipientUpdateResult = await usersCollection.updateOne(
{ _id: recipient._id }, // Query by string _id
{ $inc: { coins: data.amount } }
);
if (recipientUpdateResult.modifiedCount === 0) {
// Rollback sender's coins if recipient update failed
await usersCollection.updateOne(
{ _id: sender._id }, // Query by string _id
{ $inc: { coins: data.amount } }
);
return { success: false, message: "Failed to transfer coins to recipient. Your balance has been restored." };
}
const newSenderData = await usersCollection.findOne({ _id: sender._id }); // Query by string _id
revalidatePath("/dashboard");
return {
success: true,
message: `Successfully transferred ${data.amount} coins to ${recipient.name}.`,
newSenderBalance: newSenderData?.coins ?? sender.coins - data.amount,
};
} catch (error) {
console.error("Error transferring coins:", error);
return { success: false, message: "An unexpected error occurred during the transfer." };
}
}
export async function updateProfile(data: UpdateProfileInput): Promise<{ success: boolean; message: string }> {
const loggedInUser = await getLoggedInUser();
if (!loggedInUser) {
return { success: false, message: "You must be logged in to update your profile." };
}
try {
const db = await getDb();
const usersCollection = db.collection<User>("users");
const result = await usersCollection.updateOne(
{ _id: loggedInUser._id }, // Query by string _id
{ $set: { name: data.name } }
);
if (result.modifiedCount === 0 && result.matchedCount > 0) {
return { success: true, message: "No changes detected in profile information." };
}
if (result.modifiedCount === 0) {
return { success: false, message: "Failed to update profile. User not found." };
}
revalidatePath('/dashboard/profile');
revalidatePath('/dashboard');
return { success: true, message: "Profile updated successfully." };
} catch (error) {
console.error("Error updating profile:", error);
return { success: false, message: "An unexpected error occurred while updating profile." };
}
}
export async function changePassword(data: ChangePasswordInput): Promise<{ success: boolean; message: string }> {
const loggedInUser = await getLoggedInUser();
if (!loggedInUser) {
return { success: false, message: "You must be logged in to change your password." };
}
try {
const db = await getDb();
const usersCollection = db.collection<User>("users");
const userFromDb = await usersCollection.findOne({ _id: loggedInUser._id }); // Query by string _id
if (!userFromDb) {
return { success: false, message: "User not found." };
}
const isCurrentPasswordValid = await bcrypt.compare(data.currentPassword, userFromDb.passwordHash);
if (!isCurrentPasswordValid) {
return { success: false, message: "Incorrect current password." };
}
const newPasswordHash = await bcrypt.hash(data.newPassword, 10);
await usersCollection.updateOne(
{ _id: loggedInUser._id }, // Query by string _id
{ $set: { passwordHash: newPasswordHash } }
);
return { success: true, message: "Password changed successfully." };
} catch (error) {
console.error("Error changing password:", error);
return { success: false, message: "An unexpected error occurred while changing password." };
}
}
export async function deleteUserAccount(data: DeleteAccountInput): Promise<{ success: boolean; message: string }> {
const loggedInUser = await getLoggedInUser();
if (!loggedInUser) {
return { success: false, message: "You must be logged in to delete your account." };
}
try {
const db = await getDb();
const usersCollection = db.collection<User>("users");
const deploymentsCollection = db.collection<Deployment>("deployments");
const userFromDb = await usersCollection.findOne({ _id: loggedInUser._id }); // Query by string _id
if (!userFromDb) {
return { success: false, message: "User not found." };
}
const isCurrentPasswordValid = await bcrypt.compare(data.currentPassword, userFromDb.passwordHash);
if (!isCurrentPasswordValid) {
return { success: false, message: "Incorrect password. Account deletion failed." };
}
if (userFromDb.role === 'admin') {
const adminCount = await usersCollection.countDocuments({ role: 'admin' });
if (adminCount <= 1) {
return { success: false, message: "Cannot delete the last admin account. Promote another user to admin first." };
}
}
await deploymentsCollection.deleteMany({ userId: loggedInUser._id }); // Query by string _id
const deleteResult = await usersCollection.deleteOne({ _id: loggedInUser._id }); // Query by string _id
if (deleteResult.deletedCount === 0) {
return { success: false, message: "Failed to delete user account from the database." };
}
await logoutUser();
return { success: true, message: "Account and associated deployments deleted successfully. You have been logged out." };
} catch (error) {
console.error("Error deleting account:", error);
if (error instanceof Error && error.message.includes('NEXT_REDIRECT')) {
throw error;
}
return { success: false, message: "An unexpected error occurred while deleting your account." };
}
}
|