126 lines
4.4 KiB
TypeScript
126 lines
4.4 KiB
TypeScript
"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");
|
|
}
|