Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
225 lines
4.8 KiB
TypeScript
225 lines
4.8 KiB
TypeScript
/**
|
|
* Queries de personagens
|
|
* CRUD completo para a tabela user_characters
|
|
*/
|
|
|
|
import { query } from "../index";
|
|
|
|
export interface Character {
|
|
character_id: number;
|
|
user_id: number;
|
|
character_name: string;
|
|
server_name: string;
|
|
is_main: boolean;
|
|
verified: boolean;
|
|
created_at: Date;
|
|
}
|
|
|
|
export interface CreateCharacterData {
|
|
character_name: string;
|
|
server_name: string;
|
|
is_main?: boolean;
|
|
}
|
|
|
|
export interface UpdateCharacterData {
|
|
character_name?: string;
|
|
server_name?: string;
|
|
is_main?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Cria um novo personagem
|
|
* Se isMain=true, desmarca outros personagens como main
|
|
*/
|
|
export async function createCharacter(
|
|
userId: number,
|
|
data: CreateCharacterData
|
|
): Promise<Character> {
|
|
// Usar transação para garantir atomicidade
|
|
const { getPool } = await import("../index");
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
// Se este personagem será main, desmarcar outros
|
|
if (data.is_main) {
|
|
await client.query(
|
|
`
|
|
UPDATE user_characters
|
|
SET is_main = FALSE
|
|
WHERE user_id = $1
|
|
`,
|
|
[userId]
|
|
);
|
|
}
|
|
|
|
// Inserir novo personagem
|
|
const result = await client.query<Character>(
|
|
`
|
|
INSERT INTO user_characters (user_id, character_name, server_name, is_main)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING *
|
|
`,
|
|
[userId, data.character_name, data.server_name, data.is_main || false]
|
|
);
|
|
|
|
await client.query("COMMIT");
|
|
|
|
return result.rows[0];
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lista todos os personagens de um usuário
|
|
*/
|
|
export async function getUserCharacters(userId: number): Promise<Character[]> {
|
|
const result = await query<Character>(
|
|
`
|
|
SELECT * FROM user_characters
|
|
WHERE user_id = $1
|
|
ORDER BY is_main DESC, created_at ASC
|
|
`,
|
|
[userId]
|
|
);
|
|
|
|
return result.rows;
|
|
}
|
|
|
|
/**
|
|
* Busca personagem por ID
|
|
*/
|
|
export async function getCharacterById(
|
|
characterId: number
|
|
): Promise<Character | null> {
|
|
const result = await query<Character>(
|
|
`
|
|
SELECT * FROM user_characters WHERE character_id = $1
|
|
`,
|
|
[characterId]
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Atualiza personagem
|
|
* Verifica ownership e se isMain=true, desmarca outros
|
|
*/
|
|
export async function updateCharacter(
|
|
characterId: number,
|
|
userId: number,
|
|
data: UpdateCharacterData
|
|
): Promise<Character> {
|
|
// Verificar ownership primeiro
|
|
const existing = await getCharacterById(characterId);
|
|
if (!existing) {
|
|
throw new Error("Personagem não encontrado");
|
|
}
|
|
|
|
if (existing.user_id !== userId) {
|
|
throw new Error("Você não tem permissão para atualizar este personagem");
|
|
}
|
|
|
|
// Usar transação para garantir atomicidade
|
|
const { getPool } = await import("../index");
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
// Se este personagem será main, desmarcar outros
|
|
if (data.is_main) {
|
|
await client.query(
|
|
`
|
|
UPDATE user_characters
|
|
SET is_main = FALSE
|
|
WHERE user_id = $1 AND character_id != $2
|
|
`,
|
|
[userId, characterId]
|
|
);
|
|
}
|
|
|
|
// Construir query de update
|
|
const updates: string[] = [];
|
|
const values: any[] = [];
|
|
let paramIndex = 1;
|
|
|
|
if (data.character_name !== undefined) {
|
|
updates.push(`character_name = $${paramIndex++}`);
|
|
values.push(data.character_name);
|
|
}
|
|
|
|
if (data.server_name !== undefined) {
|
|
updates.push(`server_name = $${paramIndex++}`);
|
|
values.push(data.server_name);
|
|
}
|
|
|
|
if (data.is_main !== undefined) {
|
|
updates.push(`is_main = $${paramIndex++}`);
|
|
values.push(data.is_main);
|
|
}
|
|
|
|
if (updates.length === 0) {
|
|
// Se não há updates, retorna personagem atual
|
|
await client.query("COMMIT");
|
|
return existing;
|
|
}
|
|
|
|
values.push(characterId);
|
|
|
|
const result = await client.query<Character>(
|
|
`
|
|
UPDATE user_characters
|
|
SET ${updates.join(", ")}
|
|
WHERE character_id = $${paramIndex}
|
|
RETURNING *
|
|
`,
|
|
values
|
|
);
|
|
|
|
await client.query("COMMIT");
|
|
|
|
return result.rows[0];
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove personagem
|
|
* Verifica ownership antes de remover
|
|
*/
|
|
export async function deleteCharacter(
|
|
characterId: number,
|
|
userId: number
|
|
): Promise<void> {
|
|
// Verificar ownership
|
|
const existing = await getCharacterById(characterId);
|
|
if (!existing) {
|
|
throw new Error("Personagem não encontrado");
|
|
}
|
|
|
|
if (existing.user_id !== userId) {
|
|
throw new Error("Você não tem permissão para remover este personagem");
|
|
}
|
|
|
|
await query(
|
|
`
|
|
DELETE FROM user_characters WHERE character_id = $1
|
|
`,
|
|
[characterId]
|
|
);
|
|
}
|
|
|