This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
+113
-88
@@ -23,102 +23,127 @@ export type RedeemState = { ok: boolean; message: string } | null;
|
||||
* voucher schema carries a single `amount`, granted as the website credits
|
||||
* wallet currency.
|
||||
*/
|
||||
export async function redeem(_prev: RedeemState, formData: FormData): Promise<RedeemState> {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return { ok: false, message: "You must be signed in to redeem a voucher." };
|
||||
}
|
||||
export async function redeem(
|
||||
_prev: RedeemState,
|
||||
formData: FormData,
|
||||
): Promise<RedeemState> {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return { ok: false, message: "You must be signed in to redeem a voucher." };
|
||||
}
|
||||
|
||||
const userId = Number(session.user.id);
|
||||
if (!Number.isFinite(userId)) {
|
||||
return { ok: false, message: "Your session is invalid. Please sign in again." };
|
||||
}
|
||||
const userId = Number(session.user.id);
|
||||
if (!Number.isFinite(userId)) {
|
||||
return {
|
||||
ok: false,
|
||||
message: "Your session is invalid. Please sign in again.",
|
||||
};
|
||||
}
|
||||
|
||||
const code = String(formData.get("code") ?? "")
|
||||
.normalize("NFC")
|
||||
.trim();
|
||||
if (!code) {
|
||||
return { ok: false, message: "Please enter a voucher code." };
|
||||
}
|
||||
const code = String(formData.get("code") ?? "")
|
||||
.normalize("NFC")
|
||||
.trim();
|
||||
if (!code) {
|
||||
return { ok: false, message: "Please enter a voucher code." };
|
||||
}
|
||||
|
||||
// Look up the code (website_shop_vouchers.code is unique).
|
||||
let voucher: {
|
||||
id: bigint;
|
||||
amount: number;
|
||||
maxUses: number;
|
||||
useCount: number;
|
||||
expiresAt: Date | null;
|
||||
} | null;
|
||||
try {
|
||||
voucher = await prisma.websiteShopVouchers.findUnique({
|
||||
where: { code },
|
||||
select: { id: true, amount: true, maxUses: true, useCount: true, expiresAt: true },
|
||||
});
|
||||
} catch {
|
||||
return { ok: false, message: "We couldn't reach the server. Please try again." };
|
||||
}
|
||||
// Look up the code (website_shop_vouchers.code is unique).
|
||||
let voucher: {
|
||||
id: bigint;
|
||||
amount: number;
|
||||
maxUses: number;
|
||||
useCount: number;
|
||||
expiresAt: Date | null;
|
||||
} | null;
|
||||
try {
|
||||
voucher = await prisma.websiteShopVouchers.findUnique({
|
||||
where: { code },
|
||||
select: {
|
||||
id: true,
|
||||
amount: true,
|
||||
maxUses: true,
|
||||
useCount: true,
|
||||
expiresAt: true,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
return {
|
||||
ok: false,
|
||||
message: "We couldn't reach the server. Please try again.",
|
||||
};
|
||||
}
|
||||
|
||||
// Not found OR already expired -> generic "no active voucher" (matches AtomCMS).
|
||||
if (!voucher || (voucher.expiresAt && voucher.expiresAt.getTime() <= Date.now())) {
|
||||
return { ok: false, message: "No active voucher with the given code was found." };
|
||||
}
|
||||
// Not found OR already expired -> generic "no active voucher" (matches AtomCMS).
|
||||
if (
|
||||
!voucher ||
|
||||
(voucher.expiresAt && voucher.expiresAt.getTime() <= Date.now())
|
||||
) {
|
||||
return {
|
||||
ok: false,
|
||||
message: "No active voucher with the given code was found.",
|
||||
};
|
||||
}
|
||||
|
||||
// One redemption per user.
|
||||
try {
|
||||
const already = await prisma.websiteUsedShopVouchers.findFirst({
|
||||
where: { userId, voucherId: voucher.id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (already) {
|
||||
return { ok: false, message: "You can only use each shop voucher once." };
|
||||
}
|
||||
} catch {
|
||||
return { ok: false, message: "We couldn't reach the server. Please try again." };
|
||||
}
|
||||
// One redemption per user.
|
||||
try {
|
||||
const already = await prisma.websiteUsedShopVouchers.findFirst({
|
||||
where: { userId, voucherId: voucher.id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (already) {
|
||||
return { ok: false, message: "You can only use each shop voucher once." };
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
ok: false,
|
||||
message: "We couldn't reach the server. Please try again.",
|
||||
};
|
||||
}
|
||||
|
||||
// Record the redemption first so a successful grant can never be double-claimed.
|
||||
try {
|
||||
await prisma.websiteUsedShopVouchers.create({
|
||||
data: { userId, voucherId: voucher.id },
|
||||
});
|
||||
} catch {
|
||||
// Most likely a race (another tab redeemed it) — treat as already used.
|
||||
return { ok: false, message: "You can only use each shop voucher once." };
|
||||
}
|
||||
// Record the redemption first so a successful grant can never be double-claimed.
|
||||
try {
|
||||
await prisma.websiteUsedShopVouchers.create({
|
||||
data: { userId, voucherId: voucher.id },
|
||||
});
|
||||
} catch {
|
||||
// Most likely a race (another tab redeemed it) — treat as already used.
|
||||
return { ok: false, message: "You can only use each shop voucher once." };
|
||||
}
|
||||
|
||||
// Grant the reward. The voucher carries a single amount, delivered as credits.
|
||||
try {
|
||||
await sendCurrency({ rcon, db: prisma }, userId, "credits", voucher.amount);
|
||||
} catch {
|
||||
// sendCurrency already falls back to a direct DB write; if it still throws,
|
||||
// the used-row stands and the balance simply wasn't credited — surface that.
|
||||
return {
|
||||
ok: false,
|
||||
message: "Your voucher was accepted but the reward could not be delivered. Contact staff.",
|
||||
};
|
||||
}
|
||||
// Grant the reward. The voucher carries a single amount, delivered as credits.
|
||||
try {
|
||||
await sendCurrency({ rcon, db: prisma }, userId, "credits", voucher.amount);
|
||||
} catch {
|
||||
// sendCurrency already falls back to a direct DB write; if it still throws,
|
||||
// the used-row stands and the balance simply wasn't credited — surface that.
|
||||
return {
|
||||
ok: false,
|
||||
message:
|
||||
"Your voucher was accepted but the reward could not be delivered. Contact staff.",
|
||||
};
|
||||
}
|
||||
|
||||
// Bump use_count and expire the voucher once the cap is reached.
|
||||
try {
|
||||
const updated = await prisma.websiteShopVouchers.update({
|
||||
where: { id: voucher.id },
|
||||
data: { useCount: { increment: 1 } },
|
||||
select: { maxUses: true, useCount: true },
|
||||
});
|
||||
if (updated.maxUses && updated.useCount >= updated.maxUses) {
|
||||
await prisma.websiteShopVouchers.update({
|
||||
where: { id: voucher.id },
|
||||
data: { expiresAt: new Date() },
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Reward already delivered; the counter bump is best-effort.
|
||||
}
|
||||
// Bump use_count and expire the voucher once the cap is reached.
|
||||
try {
|
||||
const updated = await prisma.websiteShopVouchers.update({
|
||||
where: { id: voucher.id },
|
||||
data: { useCount: { increment: 1 } },
|
||||
select: { maxUses: true, useCount: true },
|
||||
});
|
||||
if (updated.maxUses && updated.useCount >= updated.maxUses) {
|
||||
await prisma.websiteShopVouchers.update({
|
||||
where: { id: voucher.id },
|
||||
data: { expiresAt: new Date() },
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Reward already delivered; the counter bump is best-effort.
|
||||
}
|
||||
|
||||
revalidatePath("/redeem");
|
||||
revalidatePath("/redeem");
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
message: `Success! Your balance has been increased by ${voucher.amount.toLocaleString()} credits.`,
|
||||
};
|
||||
return {
|
||||
ok: true,
|
||||
message: `Success! Your balance has been increased by ${voucher.amount.toLocaleString()} credits.`,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user