"use server"; import { lookupJavaProfile, updateGuildNickname, formatDiscordNickname } from "@minecraft-account-manager/minecraft"; import { minecraftAccounts, users } from "@minecraft-account-manager/database"; import { and, eq, isNull } from "drizzle-orm"; import { redirect } from "next/navigation"; import { recordUserEvent } from "@/lib/audit"; import { db } from "@/lib/database"; import { requireCurrentUser } from "@/lib/auth/user-session"; import { checkAccountAdditionNetwork, toAuditIpData } from "@/lib/ip-intelligence"; const USERNAME_PATTERN = /^[A-Za-z0-9_]{3,16}$/; export async function saveFirstName(formData: FormData) { const user = await requireCurrentUser(); const firstName = String(formData.get("firstName") ?? "").trim(); if (firstName.length < 1 || firstName.length > 50 || /[\u0000-\u001f\u007f]/.test(firstName)) { redirect("/welcome?error=invalid-name"); } await db.update(users).set({ firstName, updatedAt: new Date() }).where(eq(users.id, user.id)); await recordUserEvent(user, "games.minecraft.account-manager.user.first-name.updated", { firstName }); redirect("/welcome/minecraft"); } export async function addFirstMinecraftAccount(formData: FormData) { const user = await requireCurrentUser(); const requestedUsername = String(formData.get("username") ?? "").trim(); const confirmed = formData.get("confirmUnverified") === "yes"; if (!USERNAME_PATTERN.test(requestedUsername)) { redirect("/welcome/minecraft?error=invalid-format"); } const network = await checkAccountAdditionNetwork(); if (!network.allowed) { await recordUserEvent( user, network.reason === "blocked" ? "games.minecraft.account-manager.network.vpn-blocked" : "games.minecraft.account-manager.network.classification-unavailable", { attemptedUsername: requestedUsername, ipIntelligence: network.intelligence ? toAuditIpData(network.intelligence) : null, }, ); redirect(`/welcome/minecraft?error=${network.reason === "blocked" ? "vpn-blocked" : "ip-check-unavailable"}`); } const profile = await lookupJavaProfile(requestedUsername); if (!profile && !confirmed) { redirect(`/welcome/minecraft?unverified=${encodeURIComponent(requestedUsername)}`); } const [existingAccount] = await db .select({ id: minecraftAccounts.id }) .from(minecraftAccounts) .where(and(eq(minecraftAccounts.userId, user.id), isNull(minecraftAccounts.deletedAt))) .limit(1); let failed = false; try { await db.insert(minecraftAccounts).values({ userId: user.id, minecraftUuid: profile?.uuid ?? null, username: profile?.username ?? requestedUsername, validationStatus: profile ? "verified" : "user_confirmed", lastVerifiedAt: profile ? new Date() : null, isPrimary: !existingAccount, }); } catch { failed = true; } if (failed) redirect("/welcome/minecraft?error=already-registered"); await recordUserEvent(user, "games.minecraft.account-manager.minecraft-account.added", { username: profile?.username ?? requestedUsername, minecraftUuid: profile?.uuid ?? null, validationStatus: profile ? "verified" : "user_confirmed", }); redirect("/welcome/discord"); } export async function confirmInitialNickname() { const user = await requireCurrentUser(); const [account] = await db .select({ username: minecraftAccounts.username }) .from(minecraftAccounts) .where( and( eq(minecraftAccounts.userId, user.id), eq(minecraftAccounts.isPrimary, true), isNull(minecraftAccounts.deletedAt), ), ) .limit(1); const guildId = process.env.DISCORD_GUILD_ID?.trim(); const botToken = process.env.DISCORD_BOT_TOKEN?.trim(); if (!user.firstName || !account || !guildId || !botToken) { redirect("/welcome/discord?error=not-configured"); } try { await updateGuildNickname({ guildId, discordUserId: user.discordUserId, nickname: formatDiscordNickname(user.firstName, account.username), botToken, }); } catch { redirect("/welcome/discord?error=discord-update"); } await db .update(users) .set({ onboardingCompletedAt: new Date(), updatedAt: new Date() }) .where(eq(users.id, user.id)); await recordUserEvent(user, "games.minecraft.account-manager.discord.nickname.updated", { nickname: formatDiscordNickname(user.firstName, account.username), onboardingCompleted: true, }); redirect("/account"); }