|
@@ -0,0 +1,142 @@
|
|
|
|
|
+// KirinCode Cloud Sync Server
|
|
|
|
|
+// Bun + Elysia + Drizzle ORM + PostgreSQL
|
|
|
|
|
+
|
|
|
|
|
+import { Elysia, t } from "elysia"
|
|
|
|
|
+import { jwt } from "@elysiajs/jwt"
|
|
|
|
|
+import { cors } from "@elysiajs/cors"
|
|
|
|
|
+import { hash, compare } from "bcryptjs"
|
|
|
|
|
+import { v4 as uuid } from "uuid"
|
|
|
|
|
+
|
|
|
|
|
+const JWT_SECRET = process.env.JWT_SECRET || "kirincode-cloud-secret-change-me"
|
|
|
|
|
+const PORT = parseInt(process.env.PORT || "3000")
|
|
|
|
|
+
|
|
|
|
|
+// In-memory store (replace with PostgreSQL + Drizzle in production)
|
|
|
|
|
+const db = {
|
|
|
|
|
+ users: new Map<string, { id: string; email: string; password: string }>(),
|
|
|
|
|
+ sessions: new Map<string, any>(),
|
|
|
|
|
+ messages: new Map<string, any[]>(),
|
|
|
|
|
+ memory: new Map<string, any[]>(),
|
|
|
|
|
+ skills: new Map<string, any[]>(),
|
|
|
|
|
+ workflows: new Map<string, any[]>(),
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const app = new Elysia()
|
|
|
|
|
+ .use(cors())
|
|
|
|
|
+ .use(jwt({ name: "jwt", secret: JWT_SECRET }))
|
|
|
|
|
+
|
|
|
|
|
+ // Auth middleware
|
|
|
|
|
+ .derive(async ({ jwt, headers, set }) => {
|
|
|
|
|
+ const auth = headers["authorization"]
|
|
|
|
|
+ if (!auth?.startsWith("Bearer ")) return { userId: null }
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = await jwt.verify(auth.slice(7))
|
|
|
|
|
+ return { userId: (payload as any).sub as string }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return { userId: null }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ .guard({ beforeHandle: ({ userId, set }) => {
|
|
|
|
|
+ if (!userId) { set.status = 401; return "Unauthorized" }
|
|
|
|
|
+ }})
|
|
|
|
|
+
|
|
|
|
|
+ // === Auth ===
|
|
|
|
|
+ .post("/api/v1/auth/register", async ({ body }) => {
|
|
|
|
|
+ const id = uuid()
|
|
|
|
|
+ const password = await hash(body.password, 10)
|
|
|
|
|
+ db.users.set(id, { id, email: body.email, password })
|
|
|
|
|
+ const token = await app.decorator.jwt.sign({ sub: id })
|
|
|
|
|
+ return { token, user: { id, email: body.email } }
|
|
|
|
|
+ }, { body: t.Object({ email: t.String(), password: t.String() }) })
|
|
|
|
|
+
|
|
|
|
|
+ .post("/api/v1/auth/login", async ({ body, set }) => {
|
|
|
|
|
+ const user = Array.from(db.users.values()).find(u => u.email === body.email)
|
|
|
|
|
+ if (!user || !(await compare(body.password, user.password))) {
|
|
|
|
|
+ set.status = 401
|
|
|
|
|
+ return { error: "Invalid credentials" }
|
|
|
|
|
+ }
|
|
|
|
|
+ const token = await app.decorator.jwt.sign({ sub: user.id })
|
|
|
|
|
+ return { token, user: { id: user.id, email: user.email } }
|
|
|
|
|
+ }, { body: t.Object({ email: t.String(), password: t.String() }) })
|
|
|
|
|
+
|
|
|
|
|
+ // === Sessions ===
|
|
|
|
|
+ .get("/api/v1/sessions", async ({ userId }) => {
|
|
|
|
|
+ const list = Array.from(db.sessions.values()).filter(s => s.userId === userId)
|
|
|
|
|
+ return { data: list }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ .post("/api/v1/sessions", async ({ userId, body }) => {
|
|
|
|
|
+ const session = { id: uuid(), userId, title: body.title || "New Chat", agent: body.agent || "build", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }
|
|
|
|
|
+ db.sessions.set(session.id, session)
|
|
|
|
|
+ db.messages.set(session.id, [])
|
|
|
|
|
+ return { data: session }
|
|
|
|
|
+ }, { body: t.Object({ title: t.Optional(t.String()), agent: t.Optional(t.String()) }) })
|
|
|
|
|
+
|
|
|
|
|
+ .post("/api/v1/sessions/:id/prompt", async ({ userId, params, body }) => {
|
|
|
|
|
+ const session = db.sessions.get(params.id)
|
|
|
|
|
+ if (!session || session.userId !== userId) return { error: "Session not found" }
|
|
|
|
|
+ const msgs = db.messages.get(params.id) || []
|
|
|
|
|
+ const userMsg = { id: uuid(), role: "user", content: (body.parts || []).filter((p: any) => p.type === "text").map((p: any) => p.text).join("\n"), createdAt: new Date().toISOString() }
|
|
|
|
|
+ msgs.push(userMsg)
|
|
|
|
|
+ // AI response placeholder — in production, call LLM provider
|
|
|
|
|
+ const aiMsg = { id: uuid(), role: "assistant", content: "[AI response — connect LLM provider]", createdAt: new Date().toISOString() }
|
|
|
|
|
+ msgs.push(aiMsg)
|
|
|
|
|
+ db.messages.set(params.id, msgs)
|
|
|
|
|
+ db.sessions.set(params.id, { ...session, updatedAt: new Date().toISOString() })
|
|
|
|
|
+ return { data: { messages: msgs } }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ .get("/api/v1/sessions/:id/messages", async ({ userId, params }) => {
|
|
|
|
|
+ const session = db.sessions.get(params.id)
|
|
|
|
|
+ if (!session || session.userId !== userId) return { error: "Session not found" }
|
|
|
|
|
+ return { data: db.messages.get(params.id) || [] }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // === Memory ===
|
|
|
|
|
+ .get("/api/v1/memory", async ({ userId }) => {
|
|
|
|
|
+ return { data: db.memory.get(userId!) || [] }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ .post("/api/v1/memory", async ({ userId, body }) => {
|
|
|
|
|
+ const list = db.memory.get(userId!) || []
|
|
|
|
|
+ const entry = { id: uuid(), ...body, createdAt: new Date().toISOString() }
|
|
|
|
|
+ list.push(entry)
|
|
|
|
|
+ db.memory.set(userId!, list)
|
|
|
|
|
+ return { data: entry }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // === Skills ===
|
|
|
|
|
+ .get("/api/v1/skills", async ({ userId }) => {
|
|
|
|
|
+ return { data: db.skills.get(userId!) || [] }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ .post("/api/v1/skills", async ({ userId, body }) => {
|
|
|
|
|
+ const list = db.skills.get(userId!) || []
|
|
|
|
|
+ const skill = { id: uuid(), ...body, createdAt: new Date().toISOString() }
|
|
|
|
|
+ list.push(skill)
|
|
|
|
|
+ db.skills.set(userId!, list)
|
|
|
|
|
+ return { data: skill }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // === Workflows ===
|
|
|
|
|
+ .get("/api/v1/workflows", async ({ userId }) => {
|
|
|
|
|
+ return { data: db.workflows.get(userId!) || [] }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ .post("/api/v1/workflows", async ({ userId, body }) => {
|
|
|
|
|
+ const list = db.workflows.get(userId!) || []
|
|
|
|
|
+ const wf = { id: uuid(), ...body, createdAt: new Date().toISOString() }
|
|
|
|
|
+ list.push(wf)
|
|
|
|
|
+ db.workflows.set(userId!, list)
|
|
|
|
|
+ return { data: wf }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // === Health ===
|
|
|
|
|
+ .get("/api/v1/health", () => ({ status: "ok", version: "0.4.0" }))
|
|
|
|
|
+
|
|
|
|
|
+ .listen(PORT)
|
|
|
|
|
+
|
|
|
|
|
+console.log(`🦄 KirinCode Cloud Server running on http://localhost:${PORT}`)
|
|
|
|
|
+console.log(` API: http://localhost:${PORT}/api/v1/health`)
|
|
|
|
|
+
|
|
|
|
|
+export type App = typeof app
|