webserver

This commit is contained in:
2026-02-01 17:13:39 +01:00
parent e2f69e68cd
commit c13ffc93c0
13 changed files with 1421 additions and 1 deletions

216
src/web/api.ts Normal file
View File

@@ -0,0 +1,216 @@
/**
* API routes for bot options and personalities
*/
import { Hono } from "hono";
import { db } from "../database";
import { personalities, botOptions, guilds } from "../database/schema";
import { eq } from "drizzle-orm";
import { requireAuth } from "./session";
import * as oauth from "./oauth";
import type { BotClient } from "../core/client";
export function createApiRoutes(client: BotClient) {
const api = new Hono();
// All API routes require authentication
api.use("/*", requireAuth);
// Get guilds the user has access to (shared with Joel)
api.get("/guilds", async (c) => {
const session = c.get("session");
try {
const userGuilds = await oauth.getUserGuilds(session.accessToken);
// Get guilds that Joel is in
const botGuildIds = new Set(client.guilds.cache.map((g) => g.id));
// Filter to only guilds shared with Joel
const sharedGuilds = userGuilds.filter((g) => botGuildIds.has(g.id));
return c.json(sharedGuilds);
} catch (error) {
return c.json({ error: "Failed to fetch guilds" }, 500);
}
});
// Get personalities for a guild
api.get("/guilds/:guildId/personalities", async (c) => {
const guildId = c.req.param("guildId");
const session = c.get("session");
// Verify user has access to this guild
const hasAccess = await verifyGuildAccess(session.accessToken, guildId, client);
if (!hasAccess) {
return c.json({ error: "Access denied" }, 403);
}
const guildPersonalities = await db
.select()
.from(personalities)
.where(eq(personalities.guild_id, guildId));
return c.json(guildPersonalities);
});
// Create a personality for a guild
api.post("/guilds/:guildId/personalities", async (c) => {
const guildId = c.req.param("guildId");
const session = c.get("session");
const hasAccess = await verifyGuildAccess(session.accessToken, guildId, client);
if (!hasAccess) {
return c.json({ error: "Access denied" }, 403);
}
const body = await c.req.json<{ name: string; system_prompt: string }>();
if (!body.name || !body.system_prompt) {
return c.json({ error: "Name and system_prompt are required" }, 400);
}
const id = crypto.randomUUID();
await db.insert(personalities).values({
id,
guild_id: guildId,
name: body.name,
system_prompt: body.system_prompt,
});
return c.json({ id, guild_id: guildId, name: body.name, system_prompt: body.system_prompt }, 201);
});
// Update a personality
api.put("/guilds/:guildId/personalities/:personalityId", async (c) => {
const guildId = c.req.param("guildId");
const personalityId = c.req.param("personalityId");
const session = c.get("session");
const hasAccess = await verifyGuildAccess(session.accessToken, guildId, client);
if (!hasAccess) {
return c.json({ error: "Access denied" }, 403);
}
const body = await c.req.json<{ name?: string; system_prompt?: string }>();
await db
.update(personalities)
.set({
...body,
updated_at: new Date().toISOString(),
})
.where(eq(personalities.id, personalityId));
return c.json({ success: true });
});
// Delete a personality
api.delete("/guilds/:guildId/personalities/:personalityId", async (c) => {
const guildId = c.req.param("guildId");
const personalityId = c.req.param("personalityId");
const session = c.get("session");
const hasAccess = await verifyGuildAccess(session.accessToken, guildId, client);
if (!hasAccess) {
return c.json({ error: "Access denied" }, 403);
}
await db.delete(personalities).where(eq(personalities.id, personalityId));
return c.json({ success: true });
});
// Get bot options for a guild
api.get("/guilds/:guildId/options", async (c) => {
const guildId = c.req.param("guildId");
const session = c.get("session");
const hasAccess = await verifyGuildAccess(session.accessToken, guildId, client);
if (!hasAccess) {
return c.json({ error: "Access denied" }, 403);
}
const options = await db
.select()
.from(botOptions)
.where(eq(botOptions.guild_id, guildId))
.limit(1);
if (options.length === 0) {
// Return defaults
return c.json({
guild_id: guildId,
active_personality_id: null,
free_will_chance: 2,
memory_chance: 30,
mention_probability: 0,
});
}
return c.json(options[0]);
});
// Update bot options for a guild
api.put("/guilds/:guildId/options", async (c) => {
const guildId = c.req.param("guildId");
const session = c.get("session");
const hasAccess = await verifyGuildAccess(session.accessToken, guildId, client);
if (!hasAccess) {
return c.json({ error: "Access denied" }, 403);
}
const body = await c.req.json<{
active_personality_id?: string | null;
free_will_chance?: number;
memory_chance?: number;
mention_probability?: number;
}>();
// Upsert options
const existing = await db
.select()
.from(botOptions)
.where(eq(botOptions.guild_id, guildId))
.limit(1);
if (existing.length === 0) {
await db.insert(botOptions).values({
guild_id: guildId,
...body,
});
} else {
await db
.update(botOptions)
.set({
...body,
updated_at: new Date().toISOString(),
})
.where(eq(botOptions.guild_id, guildId));
}
return c.json({ success: true });
});
return api;
}
async function verifyGuildAccess(
accessToken: string,
guildId: string,
client: BotClient
): Promise<boolean> {
// Check if bot is in this guild
if (!client.guilds.cache.has(guildId)) {
return false;
}
// Check if user is in this guild
try {
const userGuilds = await oauth.getUserGuilds(accessToken);
return userGuilds.some((g) => g.id === guildId);
} catch {
return false;
}
}