feat(referrals): add referral attribution and daily login rewards
- Track referral attribution at registration via ?ref code with same-IP and duplicate-pair guards - Add daily login rewards with streak tracking, claim flow and sendCurrency payout backed by RCON with DB fallback - Add admin pages for referral settings and the daily reward schedule - Add migration 0033 with tables, seed schedule, settings and ACL grants - Add admin.referrals.* and admin.dailyrewards.* permission slugs - Localize new copy in en, nl and it
This commit is contained in:
1 parent
463bc2cb47
commit
c3ff497050
30 files changed
+2028
-11
No files matched your search
+19
-1
@@ -11,6 +11,7 @@ import { logger } from "@/lib/logger";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
import { captchaConfig, verifyCaptcha } from "@/lib/services/captcha";
|
||||
import { checkVpn } from "@/lib/services/ip-lookup";
|
||||
import { recordReferral } from "@/lib/services/referrals";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
const registerSchema = z.object({
|
||||
@@ -134,8 +135,9 @@ export async function register(
|
||||
}
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
let inviteeId = 0;
|
||||
try {
|
||||
await db.insert(User).values({
|
||||
const [result] = await db.insert(User).values({
|
||||
username,
|
||||
password: await hashPassword(password),
|
||||
mail: hasEmail ? mail : null,
|
||||
@@ -145,6 +147,7 @@ export async function register(
|
||||
look,
|
||||
termsAccepted: raw.termsAccepted,
|
||||
});
|
||||
inviteeId = Number(result.insertId);
|
||||
} catch (err) {
|
||||
const code = (err as { cause?: { code?: string } }).cause?.code;
|
||||
if (code === "ER_DUP_ENTRY") {
|
||||
@@ -163,6 +166,21 @@ export async function register(
|
||||
// immediate auto sign-in sees the fresh row.
|
||||
await invalidateKey(`login:user:${username}`);
|
||||
|
||||
// Referral attribution (`/register?ref=<username>`). Best-effort: a broken
|
||||
// referral must never fail the account creation.
|
||||
if (inviteeId > 0) {
|
||||
const invitedBy = String(formData.get("ref") ?? "")
|
||||
.normalize("NFC")
|
||||
.trim();
|
||||
if (invitedBy) {
|
||||
await recordReferral({
|
||||
inviterUsername: invitedBy,
|
||||
inviteeId,
|
||||
inviteeIp: ip,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verification email must never block the sign-up response — it is sent
|
||||
// after the response is flushed (no-op when mail is unconfigured).
|
||||
if (hasEmail) {
|
||||
|
||||
Reference in new issue
Block a user