DB-backed workspace + LangGraph agent
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { prisma } from "./prisma";
|
||||
import type { H3Event } from "h3";
|
||||
import { getCookie, setCookie, deleteCookie, getHeader } from "h3";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
export type AuthContext = {
|
||||
teamId: string;
|
||||
@@ -7,51 +8,85 @@ export type AuthContext = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
// Minimal temporary auth: pick from headers or auto-provision a default team/user.
|
||||
const COOKIE_USER = "cf_user";
|
||||
const COOKIE_TEAM = "cf_team";
|
||||
const COOKIE_CONV = "cf_conv";
|
||||
|
||||
function cookieOpts() {
|
||||
return {
|
||||
httpOnly: true,
|
||||
sameSite: "lax" as const,
|
||||
path: "/",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
};
|
||||
}
|
||||
|
||||
export function clearAuthSession(event: H3Event) {
|
||||
deleteCookie(event, COOKIE_USER, { path: "/" });
|
||||
deleteCookie(event, COOKIE_TEAM, { path: "/" });
|
||||
deleteCookie(event, COOKIE_CONV, { path: "/" });
|
||||
}
|
||||
|
||||
export function setSession(event: H3Event, ctx: AuthContext) {
|
||||
setCookie(event, COOKIE_USER, ctx.userId, cookieOpts());
|
||||
setCookie(event, COOKIE_TEAM, ctx.teamId, cookieOpts());
|
||||
setCookie(event, COOKIE_CONV, ctx.conversationId, cookieOpts());
|
||||
}
|
||||
|
||||
export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
||||
const cookieUser = getCookie(event, COOKIE_USER)?.trim();
|
||||
const cookieTeam = getCookie(event, COOKIE_TEAM)?.trim();
|
||||
const cookieConv = getCookie(event, COOKIE_CONV)?.trim();
|
||||
|
||||
// Temporary compatibility: allow passing via headers for debugging/dev tools.
|
||||
const hdrTeam = getHeader(event, "x-team-id")?.trim();
|
||||
const hdrUser = getHeader(event, "x-user-id")?.trim();
|
||||
const hdrConv = getHeader(event, "x-conversation-id")?.trim();
|
||||
|
||||
// Ensure default team/user exist.
|
||||
const user =
|
||||
(hdrUser ? await prisma.user.findUnique({ where: { id: hdrUser } }) : null) ??
|
||||
(await prisma.user.upsert({
|
||||
where: { id: "demo-user" },
|
||||
update: { email: "demo@clientsflow.local", name: "Demo User" },
|
||||
create: { id: "demo-user", email: "demo@clientsflow.local", name: "Demo User" },
|
||||
}));
|
||||
const hasAnySession = Boolean(cookieUser || cookieTeam || cookieConv || hdrTeam || hdrUser || hdrConv);
|
||||
if (!hasAnySession) {
|
||||
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
||||
}
|
||||
|
||||
const team =
|
||||
(hdrTeam
|
||||
? await prisma.team.findUnique({ where: { id: hdrTeam } })
|
||||
: null) ??
|
||||
(await prisma.team.upsert({
|
||||
where: { id: "demo-team" },
|
||||
update: { name: "Demo Team" },
|
||||
create: { id: "demo-team", name: "Demo Team" },
|
||||
}));
|
||||
const userId = cookieUser || hdrUser;
|
||||
const teamId = cookieTeam || hdrTeam;
|
||||
const conversationId = cookieConv || hdrConv;
|
||||
|
||||
if (!userId || !teamId || !conversationId) {
|
||||
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
const team = await prisma.team.findUnique({ where: { id: teamId } });
|
||||
const conv = await prisma.chatConversation.findUnique({ where: { id: conversationId } });
|
||||
|
||||
if (!user || !team || !conv) {
|
||||
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
||||
}
|
||||
|
||||
return { teamId: team.id, userId: user.id, conversationId: conv.id };
|
||||
}
|
||||
|
||||
export async function ensureDemoAuth() {
|
||||
const user = await prisma.user.upsert({
|
||||
where: { id: "demo-user" },
|
||||
update: { email: "demo@clientsflow.local", name: "Demo User" },
|
||||
create: { id: "demo-user", email: "demo@clientsflow.local", name: "Demo User" },
|
||||
});
|
||||
const team = await prisma.team.upsert({
|
||||
where: { id: "demo-team" },
|
||||
update: { name: "Demo Team" },
|
||||
create: { id: "demo-team", name: "Demo Team" },
|
||||
});
|
||||
await prisma.teamMember.upsert({
|
||||
where: { teamId_userId: { teamId: team.id, userId: user.id } },
|
||||
update: {},
|
||||
create: { teamId: team.id, userId: user.id, role: "OWNER" },
|
||||
});
|
||||
|
||||
const conversation =
|
||||
(hdrConv
|
||||
? await prisma.chatConversation.findUnique({ where: { id: hdrConv } })
|
||||
: null) ??
|
||||
(await prisma.chatConversation.upsert({
|
||||
where: { id: `pilot-${team.id}` },
|
||||
update: {},
|
||||
create: {
|
||||
id: `pilot-${team.id}`,
|
||||
teamId: team.id,
|
||||
createdByUserId: user.id,
|
||||
title: "Pilot",
|
||||
},
|
||||
}));
|
||||
|
||||
return { teamId: team.id, userId: user.id, conversationId: conversation.id };
|
||||
const conv = await prisma.chatConversation.upsert({
|
||||
where: { id: `pilot-${team.id}` },
|
||||
update: {},
|
||||
create: { id: `pilot-${team.id}`, teamId: team.id, createdByUserId: user.id, title: "Pilot" },
|
||||
});
|
||||
return { teamId: team.id, userId: user.id, conversationId: conv.id };
|
||||
}
|
||||
|
||||
22
Frontend/server/utils/redis.ts
Normal file
22
Frontend/server/utils/redis.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import Redis from "ioredis";
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __redis: Redis | undefined;
|
||||
}
|
||||
|
||||
export function getRedis() {
|
||||
if (globalThis.__redis) return globalThis.__redis;
|
||||
|
||||
const url = process.env.REDIS_URL || "redis://localhost:6379";
|
||||
const client = new Redis(url, {
|
||||
maxRetriesPerRequest: null, // recommended for BullMQ
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalThis.__redis = client;
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
29
Frontend/server/utils/telegram.ts
Normal file
29
Frontend/server/utils/telegram.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type TelegramUpdate = Record<string, any>;
|
||||
|
||||
export function telegramApiBase() {
|
||||
return process.env.TELEGRAM_API_BASE || "https://api.telegram.org";
|
||||
}
|
||||
|
||||
export function requireTelegramBotToken() {
|
||||
const token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
if (!token) throw new Error("TELEGRAM_BOT_TOKEN is required");
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function telegramBotApi<T>(method: string, body: unknown): Promise<T> {
|
||||
const token = requireTelegramBotToken();
|
||||
const res = await fetch(`${telegramApiBase()}/bot${token}/${method}`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const json = (await res.json().catch(() => null)) as any;
|
||||
if (!res.ok || !json?.ok) {
|
||||
const desc = json?.description || `HTTP ${res.status}`;
|
||||
throw new Error(`Telegram API ${method} failed: ${desc}`);
|
||||
}
|
||||
|
||||
return json.result as T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user