import { Hono } from "hono"; import { db } from "../db/client.js"; import { users, vk_connections } from "../db/schema.js"; import { eq } from "drizzle-orm"; import { getSessionUserId, type AppEnv } from "../lib/auth.js"; import { render } from "../lib/render.js"; import { config } from "../config.js"; export const vkRouter = new Hono(); const VK_API_URL = "https://api.vk.com/method"; const VK_OAUTH_URL = "https://oauth.vk.com/authorize"; async function requireUser(c: Parameters[0]) { const id = getSessionUserId(c); if (!id) return null; const [user] = await db.select().from(users).where(eq(users.id, id)).limit(1); return user ?? null; } async function fetchGroupInfo(token: string): Promise<{ groupId: string | null; groupName: string | null }> { try { const params = new URLSearchParams({ access_token: token, v: config.VK_API_VERSION, filter: "admin", extended: "1", count: "1", }); const resp = await fetch(`${VK_API_URL}/groups.get?${params}`, { signal: AbortSignal.timeout(15000), }); if (!resp.ok) return { groupId: null, groupName: null }; const data = await resp.json() as { error?: unknown; response?: { items?: Array<{ id: number; name: string }> } }; if (data.error) return { groupId: null, groupName: null }; const items = data.response?.items ?? []; if (items.length === 0) return { groupId: null, groupName: null }; return { groupId: String(items[0].id), groupName: items[0].name }; } catch { return { groupId: null, groupName: null }; } } vkRouter.get("/vk", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); const [connection] = await db.select().from(vk_connections).where(eq(vk_connections.user_id, user.id)).limit(1); const error = c.req.query("error") ?? null; return c.html(render("vk.njk", { user, connection: connection ?? null, error, vk_client_id: config.VK_CLIENT_ID, callback_url: `${config.BASE_URL}/vk/callback`, })); }); vkRouter.get("/vk/connect", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); if (!config.VK_CLIENT_ID) return c.redirect("/vk?error=no_client_id", 303); const params = new URLSearchParams({ client_id: config.VK_CLIENT_ID, scope: "market,groups", redirect_uri: `${config.BASE_URL}/vk/callback`, display: "page", response_type: "token", v: config.VK_API_VERSION, }); return c.redirect(`${VK_OAUTH_URL}?${params}`, 302); }); vkRouter.get("/vk/callback", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); return c.html(render("vk_callback.njk", { user })); }); vkRouter.post("/vk/token", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); const form = await c.req.formData(); const token = String(form.get("token") ?? "").trim(); if (!token) return c.redirect("/vk?error=empty_token", 303); const { groupId, groupName } = await fetchGroupInfo(token); if (!groupId) return c.redirect("/vk?error=invalid_token", 303); const now = new Date(); const [existing] = await db.select().from(vk_connections).where(eq(vk_connections.user_id, user.id)).limit(1); if (existing) { await db.update(vk_connections) .set({ access_token: token, vk_user_id: groupId, first_name: groupName, last_name: null, is_online: true, last_checked_at: now, updated_at: now }) .where(eq(vk_connections.id, existing.id)); } else { await db.insert(vk_connections).values({ user_id: user.id, access_token: token, vk_user_id: groupId, first_name: groupName, last_name: null, is_online: true, last_checked_at: now, }); } return c.redirect("/connections", 303); }); vkRouter.post("/vk/disconnect", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); await db.delete(vk_connections).where(eq(vk_connections.user_id, user.id)); return c.redirect("/connections", 303); });