feat(admin): ban appeals, photos polish, economy adjust, staff smoke
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
fe46bd0544
commit
3ad3f9512c
36 files changed
+1185
-293
No files matched your search
@@ -181,3 +181,139 @@ export async function bulkGiveBadge({
|
||||
data: { given, total: userIds.length, failedIds },
|
||||
};
|
||||
}
|
||||
|
||||
export async function bulkAdjustCurrency({
|
||||
userIds,
|
||||
amount,
|
||||
type,
|
||||
}: {
|
||||
userIds: number[];
|
||||
/** Positive = give, negative = take. Balances clamped at 0. */
|
||||
amount: number;
|
||||
type: "credits" | "pixels" | "points";
|
||||
}): Promise<
|
||||
ActionResult<{
|
||||
adjusted: number;
|
||||
total: number;
|
||||
failedIds: Array<{ userId: number; reason: string }>;
|
||||
}>
|
||||
> {
|
||||
const staff = await requirePermission(PERMS.USERS_EDIT);
|
||||
if (!Number.isFinite(amount) || amount === 0) {
|
||||
return { ok: false as const, error: "Amount must be a non-zero number" };
|
||||
}
|
||||
|
||||
if (amount > 0) {
|
||||
const given = await bulkGiveCurrency({ userIds, amount, type });
|
||||
if (!given.ok) return given;
|
||||
if (!given.data) {
|
||||
return { ok: false as const, error: "Currency adjustment failed" };
|
||||
}
|
||||
return {
|
||||
ok: true as const,
|
||||
data: {
|
||||
adjusted: given.data.given,
|
||||
total: given.data.total,
|
||||
failedIds: given.data.failedIds,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const take = Math.abs(Math.trunc(amount));
|
||||
let adjusted = 0;
|
||||
const failedIds: Array<{ userId: number; reason: string }> = [];
|
||||
|
||||
for (const userId of userIds) {
|
||||
try {
|
||||
if (type === "credits") {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { credits: true },
|
||||
});
|
||||
if (!user) {
|
||||
failedIds.push({ userId, reason: "Not found" });
|
||||
continue;
|
||||
}
|
||||
const next = Math.max(0, user.credits - take);
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { credits: next },
|
||||
});
|
||||
} else {
|
||||
const currencyType = type === "pixels" ? 0 : 101;
|
||||
const row = await prisma.usersCurrency.findUnique({
|
||||
where: { userId_type: { userId, type: currencyType } },
|
||||
});
|
||||
const current = row?.amount ?? 0;
|
||||
const next = Math.max(0, current - take);
|
||||
await prisma.usersCurrency.upsert({
|
||||
where: { userId_type: { userId, type: currencyType } },
|
||||
update: { amount: next },
|
||||
create: { userId, type: currencyType, amount: next },
|
||||
});
|
||||
}
|
||||
adjusted++;
|
||||
} catch {
|
||||
failedIds.push({ userId, reason: "Database error" });
|
||||
}
|
||||
}
|
||||
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bulk_adjust_currency",
|
||||
description: `Adjusted ${amount} ${type} for ${adjusted} user(s) (DB-only take; no RCON debit)`,
|
||||
targetType: "user",
|
||||
});
|
||||
return {
|
||||
ok: true as const,
|
||||
data: { adjusted, total: userIds.length, failedIds },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist trade lock on `sanctions.trade_locked_until`.
|
||||
* No first-class RCON trade-lock helper in this CMS — DB only.
|
||||
*/
|
||||
export async function setTradeLock({
|
||||
userId,
|
||||
untilUnix,
|
||||
}: {
|
||||
userId: number;
|
||||
/** Unix seconds; 0 clears the lock. */
|
||||
untilUnix: number;
|
||||
}): Promise<ActionResult<{ userId: number; untilUnix: number }>> {
|
||||
const staff = await requirePermission(PERMS.USERS_EDIT);
|
||||
const until = Math.max(0, Math.trunc(untilUnix));
|
||||
|
||||
const existing = await prisma.sanctions.findFirst({
|
||||
where: { habboId: userId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (existing) {
|
||||
await prisma.sanctions.update({
|
||||
where: { id: existing.id },
|
||||
data: { tradeLockedUntil: until },
|
||||
});
|
||||
} else {
|
||||
await prisma.sanctions.create({
|
||||
data: {
|
||||
habboId: userId,
|
||||
tradeLockedUntil: until,
|
||||
reason: until > 0 ? "Trade lock (CMS)" : "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: until > 0 ? "trade_lock" : "trade_unlock",
|
||||
description:
|
||||
until > 0
|
||||
? `Trade-locked user #${userId} until ${until}`
|
||||
: `Cleared trade lock for user #${userId}`,
|
||||
targetType: "user",
|
||||
targetId: userId,
|
||||
});
|
||||
|
||||
return { ok: true as const, data: { userId, untilUnix: until } };
|
||||
}
|
||||
Reference in new issue
Block a user