diff --git a/sources/app/api/api.ts b/sources/app/api/api.ts index 66e2a45..b7b2d44 100644 --- a/sources/app/api/api.ts +++ b/sources/app/api/api.ts @@ -2,7 +2,7 @@ import fastify from "fastify"; import { log, logger } from "@/utils/log"; import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "fastify-type-provider-zod"; import { onShutdown } from "@/utils/shutdown"; -import { EventRouter } from "@/modules/eventRouter"; +import { EventRouter } from "@/app/events/eventRouter"; import { Fastify } from "./types"; import { authRoutes } from "./routes/authRoutes"; import { pushRoutes } from "./routes/pushRoutes"; diff --git a/sources/app/api/routes/accountRoutes.ts b/sources/app/api/routes/accountRoutes.ts index 2afbbbf..8c2b0b5 100644 --- a/sources/app/api/routes/accountRoutes.ts +++ b/sources/app/api/routes/accountRoutes.ts @@ -1,4 +1,4 @@ -import { EventRouter, buildUpdateAccountUpdate } from "@/modules/eventRouter"; +import { EventRouter, buildUpdateAccountUpdate } from "@/app/events/eventRouter"; import { db } from "@/storage/db"; import { Fastify } from "../types"; import { getPublicUrl } from "@/storage/files"; @@ -6,6 +6,7 @@ import { z } from "zod"; import { randomKeyNaked } from "@/utils/randomKeyNaked"; import { allocateUserSeq } from "@/storage/seq"; import { log } from "@/utils/log"; +import { AccountProfile } from "@/types"; export function accountRoutes(app: Fastify, eventRouter: EventRouter) { app.get('/v1/account/profile', { @@ -21,13 +22,15 @@ export function accountRoutes(app: Fastify, eventRouter: EventRouter) { githubUser: true } }); + const connectedVendors = new Set((await db.serviceAccountToken.findMany({ where: { accountId: userId } })).map(t => t.vendor)); return reply.send({ id: userId, timestamp: Date.now(), firstName: user.firstName, lastName: user.lastName, avatar: user.avatar ? { ...user.avatar, url: getPublicUrl(user.avatar.path) } : null, - github: user.githubUser ? user.githubUser.profile : null + github: user.githubUser ? user.githubUser.profile : null, + connectedServices: Array.from(connectedVendors) }); }); diff --git a/sources/app/api/routes/connectRoutes.ts b/sources/app/api/routes/connectRoutes.ts index af2f215..bfc546d 100644 --- a/sources/app/api/routes/connectRoutes.ts +++ b/sources/app/api/routes/connectRoutes.ts @@ -6,12 +6,12 @@ import { db } from "@/storage/db"; import { Prisma } from "@prisma/client"; import { allocateUserSeq } from "@/storage/seq"; import { randomKeyNaked } from "@/utils/randomKeyNaked"; -import { buildUpdateAccountUpdate } from "@/modules/eventRouter"; +import { buildUpdateAccountUpdate } from "@/app/events/eventRouter"; import { GitHubProfile } from "../types"; import { separateName } from "@/utils/separateName"; import { uploadImage } from "@/storage/uploadImage"; -import { EventRouter } from "@/modules/eventRouter"; -import { encryptString } from "@/modules/encrypt"; +import { EventRouter } from "@/app/events/eventRouter"; +import { decryptString, encryptString } from "@/modules/encrypt"; export function connectRoutes(app: Fastify, eventRouter: EventRouter) { @@ -359,4 +359,95 @@ export function connectRoutes(app: Fastify, eventRouter: EventRouter) { return reply.code(500).send({ error: 'Failed to disconnect GitHub account' }); } }); + + // + // Inference endpoints + // + + app.post('/v1/connect/:vendor/register', { + preHandler: app.authenticate, + schema: { + body: z.object({ + token: z.string() + }), + params: z.object({ + vendor: z.enum(['openai', 'anthropic', 'gemini']) + }) + } + }, async (request, reply) => { + const userId = request.userId; + const encrypted = encryptString(['user', userId, 'vendors', request.params.vendor, 'token'], request.body.token); + await db.serviceAccountToken.upsert({ + where: { accountId_vendor: { accountId: userId, vendor: request.params.vendor } }, + update: { updatedAt: new Date(), token: encrypted }, + create: { accountId: userId, vendor: request.params.vendor, token: encrypted } + }); + reply.send({ success: true }); + }); + + app.get('/v1/connect/:vendor/token', { + preHandler: app.authenticate, + schema: { + params: z.object({ + vendor: z.enum(['openai', 'anthropic', 'gemini']) + }), + response: { + 200: z.object({ + token: z.string().nullable() + }) + } + } + }, async (request, reply) => { + const userId = request.userId; + const token = await db.serviceAccountToken.findUnique({ + where: { accountId_vendor: { accountId: userId, vendor: request.params.vendor } }, + select: { token: true } + }); + if (!token) { + return reply.send({ token: null }); + } else { + return reply.send({ token: decryptString(['user', userId, 'vendors', request.params.vendor, 'token'], token.token) }); + } + }); + + app.delete('/v1/connect/:vendor', { + preHandler: app.authenticate, + schema: { + params: z.object({ + vendor: z.enum(['openai', 'anthropic', 'gemini']) + }), + response: { + 200: z.object({ + success: z.literal(true) + }) + } + } + }, async (request, reply) => { + const userId = request.userId; + await db.serviceAccountToken.delete({ where: { accountId_vendor: { accountId: userId, vendor: request.params.vendor } } }); + reply.send({ success: true }); + }); + + app.get('/v1/connect/tokens', { + preHandler: app.authenticate, + schema: { + response: { + 200: z.object({ + tokens: z.array(z.object({ + vendor: z.string(), + token: z.string() + })) + }) + } + } + }, async (request, reply) => { + const userId = request.userId; + const tokens = await db.serviceAccountToken.findMany({ where: { accountId: userId } }); + let decrypted = []; + for (const token of tokens) { + decrypted.push({ vendor: token.vendor, token: decryptString(['user', userId, 'vendors', token.vendor, 'token'], token.token) }); + } + return reply.send({ tokens: decrypted }); + }); + } \ No newline at end of file diff --git a/sources/app/api/routes/machinesRoutes.ts b/sources/app/api/routes/machinesRoutes.ts index d273c54..4701a06 100644 --- a/sources/app/api/routes/machinesRoutes.ts +++ b/sources/app/api/routes/machinesRoutes.ts @@ -1,11 +1,11 @@ -import { EventRouter } from "@/modules/eventRouter"; +import { EventRouter } from "@/app/events/eventRouter"; import { Fastify } from "../types"; import { z } from "zod"; import { db } from "@/storage/db"; import { log } from "@/utils/log"; import { randomKeyNaked } from "@/utils/randomKeyNaked"; import { allocateUserSeq } from "@/storage/seq"; -import { buildUpdateMachineUpdate } from "@/modules/eventRouter"; +import { buildUpdateMachineUpdate } from "@/app/events/eventRouter"; export function machinesRoutes(app: Fastify, eventRouter: EventRouter) { app.post('/v1/machines', { diff --git a/sources/app/api/routes/sessionRoutes.ts b/sources/app/api/routes/sessionRoutes.ts index 90e3ea9..f1ddbcf 100644 --- a/sources/app/api/routes/sessionRoutes.ts +++ b/sources/app/api/routes/sessionRoutes.ts @@ -1,4 +1,4 @@ -import { EventRouter, buildNewSessionUpdate } from "@/modules/eventRouter"; +import { EventRouter, buildNewSessionUpdate } from "@/app/events/eventRouter"; import { type Fastify } from "../types"; import { db } from "@/storage/db"; import { z } from "zod"; diff --git a/sources/app/api/socket.ts b/sources/app/api/socket.ts index 8e8a7f9..1af2ff4 100644 --- a/sources/app/api/socket.ts +++ b/sources/app/api/socket.ts @@ -1,6 +1,6 @@ import { onShutdown } from "@/utils/shutdown"; import { Fastify } from "./types"; -import { buildMachineActivityEphemeral, ClientConnection, EventRouter } from "@/modules/eventRouter"; +import { buildMachineActivityEphemeral, ClientConnection, EventRouter } from "@/app/events/eventRouter"; import { Server, Socket } from "socket.io"; import { log } from "@/utils/log"; import { auth } from "@/app/auth/auth"; diff --git a/sources/app/api/socket/machineUpdateHandler.ts b/sources/app/api/socket/machineUpdateHandler.ts index 16483ff..3d6aaa0 100644 --- a/sources/app/api/socket/machineUpdateHandler.ts +++ b/sources/app/api/socket/machineUpdateHandler.ts @@ -1,6 +1,6 @@ import { machineAliveEventsCounter, websocketEventsCounter } from "@/app/monitoring/metrics2"; import { activityCache } from "@/app/presence/sessionCache"; -import { buildMachineActivityEphemeral, buildUpdateMachineUpdate, EventRouter } from "@/modules/eventRouter"; +import { buildMachineActivityEphemeral, buildUpdateMachineUpdate, EventRouter } from "@/app/events/eventRouter"; import { log } from "@/utils/log"; import { db } from "@/storage/db"; import { Socket } from "socket.io"; diff --git a/sources/app/api/socket/rpcHandler.ts b/sources/app/api/socket/rpcHandler.ts index 0523082..1f29674 100644 --- a/sources/app/api/socket/rpcHandler.ts +++ b/sources/app/api/socket/rpcHandler.ts @@ -1,4 +1,4 @@ -import { EventRouter } from "@/modules/eventRouter"; +import { EventRouter } from "@/app/events/eventRouter"; import { log } from "@/utils/log"; import { Socket } from "socket.io"; diff --git a/sources/app/api/socket/sessionUpdateHandler.ts b/sources/app/api/socket/sessionUpdateHandler.ts index 2d41a8c..8f6c5fb 100644 --- a/sources/app/api/socket/sessionUpdateHandler.ts +++ b/sources/app/api/socket/sessionUpdateHandler.ts @@ -1,6 +1,6 @@ import { sessionAliveEventsCounter, websocketEventsCounter } from "@/app/monitoring/metrics2"; import { activityCache } from "@/app/presence/sessionCache"; -import { buildNewMessageUpdate, buildSessionActivityEphemeral, buildUpdateSessionUpdate, ClientConnection, EventRouter } from "@/modules/eventRouter"; +import { buildNewMessageUpdate, buildSessionActivityEphemeral, buildUpdateSessionUpdate, ClientConnection, EventRouter } from "@/app/events/eventRouter"; import { db } from "@/storage/db"; import { allocateSessionSeq, allocateUserSeq } from "@/storage/seq"; import { AsyncLock } from "@/utils/lock"; diff --git a/sources/app/api/socket/usageHandler.ts b/sources/app/api/socket/usageHandler.ts index 244d7c9..2d59f7c 100644 --- a/sources/app/api/socket/usageHandler.ts +++ b/sources/app/api/socket/usageHandler.ts @@ -1,7 +1,7 @@ import { Socket } from "socket.io"; import { AsyncLock } from "@/utils/lock"; import { db } from "@/storage/db"; -import { buildUsageEphemeral, EventRouter } from "@/modules/eventRouter"; +import { buildUsageEphemeral, EventRouter } from "@/app/events/eventRouter"; import { log } from "@/utils/log"; export function usageHandler(userId: string, socket: Socket, eventRouter: EventRouter) { diff --git a/sources/modules/eventRouter.ts b/sources/app/events/eventRouter.ts similarity index 100% rename from sources/modules/eventRouter.ts rename to sources/app/events/eventRouter.ts diff --git a/sources/app/presence/timeout.ts b/sources/app/presence/timeout.ts index 977e02a..4747cbc 100644 --- a/sources/app/presence/timeout.ts +++ b/sources/app/presence/timeout.ts @@ -2,7 +2,7 @@ import { db } from "@/storage/db"; import { delay } from "@/utils/delay"; import { forever } from "@/utils/forever"; import { shutdownSignal } from "@/utils/shutdown"; -import { buildMachineActivityEphemeral, buildSessionActivityEphemeral, EventRouter } from "@/modules/eventRouter"; +import { buildMachineActivityEphemeral, buildSessionActivityEphemeral, EventRouter } from "@/app/events/eventRouter"; export function startTimeout(eventRouter: EventRouter) { forever('session-timeout', async () => { diff --git a/sources/main.ts b/sources/main.ts index c5155f5..73e5118 100644 --- a/sources/main.ts +++ b/sources/main.ts @@ -11,7 +11,7 @@ import { startDatabaseMetricsUpdater } from "@/app/monitoring/metrics2"; import { initEncrypt } from "./modules/encrypt"; import { initGithub } from "./modules/github"; import { loadFiles } from "./storage/files"; -import { EventRouter } from "./modules/eventRouter"; +import { EventRouter } from "./app/events/eventRouter"; async function main() { diff --git a/sources/modules/encrypt.ts b/sources/modules/encrypt.ts index cde51a0..2d00d4a 100644 --- a/sources/modules/encrypt.ts +++ b/sources/modules/encrypt.ts @@ -18,7 +18,7 @@ export function encryptBytes(path: string[], bytes: Uint8Array) { } export function decryptString(path: string[], encrypted: Uint8Array) { - return keyTree!.symmetricDecryptBuffer(path, encrypted); + return keyTree!.symmetricDecryptString(path, encrypted); } export function decryptBytes(path: string[], encrypted: Uint8Array) { diff --git a/sources/types.ts b/sources/types.ts index 420953a..89e4a16 100644 --- a/sources/types.ts +++ b/sources/types.ts @@ -10,4 +10,5 @@ export type AccountProfile = { value: string | null; version: number; } | null; + connectedServices: string[]; } \ No newline at end of file