feat: Añadir funcionalidad para actualizar la configuración del servidor desde el panel de ajustes del dashboard
This commit is contained in:
@@ -952,6 +952,116 @@ export const server = createServer(
|
|||||||
return res.end();
|
return res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API: update guild settings (used by dashboard settings panel)
|
||||||
|
if (req.method === "POST" && url.pathname.startsWith("/api/dashboard/")) {
|
||||||
|
const partsApi = url.pathname.split("/").filter(Boolean);
|
||||||
|
// expected /api/dashboard/:guildId/settings
|
||||||
|
if (
|
||||||
|
partsApi[0] === "api" &&
|
||||||
|
partsApi[1] === "dashboard" &&
|
||||||
|
partsApi.length >= 4
|
||||||
|
) {
|
||||||
|
const guildId = partsApi[2];
|
||||||
|
const action = partsApi[3];
|
||||||
|
if (action === "settings") {
|
||||||
|
const cookiesApi = parseCookies(req);
|
||||||
|
const signedApi = cookiesApi["amayo_sid"];
|
||||||
|
const sidApi = unsignSid(signedApi);
|
||||||
|
const sessionApi = sidApi ? SESSIONS.get(sidApi) : null;
|
||||||
|
if (!sessionApi) {
|
||||||
|
res.writeHead(
|
||||||
|
401,
|
||||||
|
applySecurityHeadersForRequest(req, {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
res.end(JSON.stringify({ error: "not_authenticated" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ensure user has this guild in their session guilds (basic guard)
|
||||||
|
const userGuildsApi = sessionApi?.guilds || [];
|
||||||
|
if (
|
||||||
|
!userGuildsApi.find((g: any) => String(g.id) === String(guildId))
|
||||||
|
) {
|
||||||
|
res.writeHead(
|
||||||
|
403,
|
||||||
|
applySecurityHeadersForRequest(req, {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
res.end(JSON.stringify({ error: "forbidden" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect body
|
||||||
|
const raw = await new Promise<string>((resolve, reject) => {
|
||||||
|
let data = "";
|
||||||
|
req.on("data", (c: any) => (data += String(c)));
|
||||||
|
req.on("end", () => resolve(data));
|
||||||
|
req.on("error", (e: any) => reject(e));
|
||||||
|
}).catch(() => "");
|
||||||
|
|
||||||
|
let payload: any = {};
|
||||||
|
try {
|
||||||
|
payload = raw ? JSON.parse(raw) : {};
|
||||||
|
} catch {
|
||||||
|
payload = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPrefix = sanitizeString(payload.prefix ?? "");
|
||||||
|
const newAi =
|
||||||
|
payload.aiRolePrompt == null
|
||||||
|
? null
|
||||||
|
: String(payload.aiRolePrompt).slice(0, 1500);
|
||||||
|
let staff: string[] | null = null;
|
||||||
|
if (Array.isArray(payload.staff)) {
|
||||||
|
staff = payload.staff
|
||||||
|
.map(String)
|
||||||
|
.filter((s) => validateDiscordId(s));
|
||||||
|
} else if (typeof payload.staff === "string") {
|
||||||
|
const arr = payload.staff
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
staff = arr.filter((s) => validateDiscordId(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updateData: any = {};
|
||||||
|
if (newPrefix) updateData.prefix = newPrefix;
|
||||||
|
// allow explicitly setting null to remove ai prompt
|
||||||
|
updateData.aiRolePrompt = newAi;
|
||||||
|
if (staff !== null) updateData.staff = staff;
|
||||||
|
|
||||||
|
const createData: any = {
|
||||||
|
id: String(guildId),
|
||||||
|
name: String(guildId),
|
||||||
|
prefix: newPrefix || "!",
|
||||||
|
aiRolePrompt: newAi,
|
||||||
|
staff: staff || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
await prisma.guild.upsert({
|
||||||
|
where: { id: String(guildId) },
|
||||||
|
update: updateData,
|
||||||
|
create: createData,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Failed saving guild settings", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(
|
||||||
|
200,
|
||||||
|
applySecurityHeadersForRequest(req, {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
res.end(JSON.stringify({ ok: true }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard routes
|
// Dashboard routes
|
||||||
if (
|
if (
|
||||||
url.pathname === "/dashboard" ||
|
url.pathname === "/dashboard" ||
|
||||||
@@ -1048,6 +1158,15 @@ export const server = createServer(
|
|||||||
// find a nicer display name for selected guild
|
// find a nicer display name for selected guild
|
||||||
const found = guilds.find((g) => String(g.id) === String(guildId));
|
const found = guilds.find((g) => String(g.id) === String(guildId));
|
||||||
const selectedGuildName = found ? found.name : guildId;
|
const selectedGuildName = found ? found.name : guildId;
|
||||||
|
// Load guild config from DB to allow editing settings
|
||||||
|
let guildConfig: any = null;
|
||||||
|
try {
|
||||||
|
guildConfig = await prisma.guild.findFirst({
|
||||||
|
where: { id: String(guildId) },
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
guildConfig = null;
|
||||||
|
}
|
||||||
// Render dashboard with selected guild context; show dashboard nav
|
// Render dashboard with selected guild context; show dashboard nav
|
||||||
await renderTemplate(req, res, "dashboard", {
|
await renderTemplate(req, res, "dashboard", {
|
||||||
appName: pkg.name ?? "Amayo Bot",
|
appName: pkg.name ?? "Amayo Bot",
|
||||||
@@ -1056,6 +1175,7 @@ export const server = createServer(
|
|||||||
selectedGuild: guildId,
|
selectedGuild: guildId,
|
||||||
selectedGuildId: guildId,
|
selectedGuildId: guildId,
|
||||||
selectedGuildName,
|
selectedGuildName,
|
||||||
|
guildConfig,
|
||||||
page,
|
page,
|
||||||
hideNavbar: false,
|
hideNavbar: false,
|
||||||
useDashboardNav: true,
|
useDashboardNav: true,
|
||||||
|
|||||||
@@ -44,6 +44,66 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
<% if (typeof page !== 'undefined' && page === 'settings' && selectedGuild) { %>
|
||||||
|
<div class="w-full max-w-3xl mt-6">
|
||||||
|
<div class="backdrop-blur-md bg-white/6 border border-white/8 rounded-xl p-6 glass-card">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Ajustes del servidor</h2>
|
||||||
|
<form id="guildSettingsForm" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm text-slate-200 mb-1">Prefix del bot</label>
|
||||||
|
<input type="text" name="prefix" id="prefixInput" value="<%= (guildConfig && guildConfig.prefix) || '' %>" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="!" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm text-slate-200 mb-1">AI Role Prompt (opcional)</label>
|
||||||
|
<textarea name="aiRolePrompt" id="aiRoleInput" rows="4" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="E.g. Actúa como un moderador amigable..."><%= (guildConfig && guildConfig.aiRolePrompt) || '' %></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm text-slate-200 mb-1">Roles de staff (IDs separadas por coma)</label>
|
||||||
|
<input type="text" name="staff" id="staffInput" value="<%= (guildConfig && (Array.isArray(guildConfig.staff) ? guildConfig.staff.join(',') : guildConfig.staff)) || '' %>" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="123... , 456..." />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button type="submit" class="pixel-btn">Guardar</button>
|
||||||
|
<span id="saveStatus" class="text-sm text-slate-300"></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const form = document.getElementById('guildSettingsForm');
|
||||||
|
const status = document.getElementById('saveStatus');
|
||||||
|
form.addEventListener('submit', async (e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
status.textContent = 'Guardando...';
|
||||||
|
const prefix = document.getElementById('prefixInput').value.trim();
|
||||||
|
const aiRolePrompt = document.getElementById('aiRoleInput').value.trim();
|
||||||
|
const staffRaw = document.getElementById('staffInput').value.trim();
|
||||||
|
const payload = {
|
||||||
|
prefix: prefix,
|
||||||
|
aiRolePrompt: aiRolePrompt.length ? aiRolePrompt : null,
|
||||||
|
staff: staffRaw ? staffRaw.split(',').map(s=>s.trim()).filter(Boolean) : [],
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/dashboard/${encodeURIComponent('<%= selectedGuild %>')}/settings`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (res.ok && json.ok) {
|
||||||
|
status.textContent = 'Guardado';
|
||||||
|
setTimeout(()=> status.textContent = '', 2500);
|
||||||
|
} else {
|
||||||
|
status.textContent = json.error || 'Error';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
status.textContent = 'Error de red';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user