diff --git a/opencodetmp/opencode-linux-x64.zip b/opencodetmp/opencode-linux-x64.zip new file mode 100644 index 00000000..9ab11df4 Binary files /dev/null and b/opencodetmp/opencode-linux-x64.zip differ diff --git a/packages/core/src/session/index.ts b/packages/core/src/session/index.ts index f37262fe..d9add3cc 100644 --- a/packages/core/src/session/index.ts +++ b/packages/core/src/session/index.ts @@ -1,5 +1,7 @@ import { z } from "zod/v4"; import { fn } from "../utils/fn.js"; +import { Identifier } from "../id.js"; +import { and, eq, isNull } from "drizzle-orm"; import { sessionTable } from "./session.sql.js"; import { MachinePresetName, @@ -12,6 +14,8 @@ import { type Session as SessionInterface, type Config as SessionConfig, } from "./types.js"; +import { Actor } from "../actor.js"; +import { Database } from "../drizzle/index.js"; // Re-export all types and schemas from types.ts export { @@ -49,9 +53,43 @@ export namespace Session { run: (ctx: any) => Promise; }; - export const list = fn(z.void(), () => { - // Placeholder implementation - }); + export const create = fn(Info.partial({ id: true }), (input) => + Database.transaction(async (tx) => { + const id = Identifier.descending("session", input.id); // Placeholder for Identifier.ascending + + await tx + .insert(sessionTable) + .values({ + id, + ownerID: Actor.userID(), + ...input, + }) + .onConflictDoUpdate({ + target: sessionTable.id, + set: { + timeDeleted: null, + }, + }); + + return id; + }), + ); + + export const list = fn(z.void(), () => + Database.use((tx) => + tx + .select() + .from(sessionTable) + .where( + and( + eq(sessionTable.ownerID, Actor.userID()), + isNull(sessionTable.timeDeleted), + ), + ) + .limit(100) + .then((rows) => rows.map(serialize)), + ), + ); export function serialize( input: typeof sessionTable.$inferSelect, diff --git a/packages/core/src/session/session.sql.ts b/packages/core/src/session/session.sql.ts index e3d0eedb..68956232 100644 --- a/packages/core/src/session/session.sql.ts +++ b/packages/core/src/session/session.sql.ts @@ -1,4 +1,4 @@ -import { id, timestamps } from "../drizzle/types.js"; +import { id, timestamps, ulid } from "../drizzle/types.js"; import { pgTable, varchar, @@ -20,11 +20,17 @@ import { AudioRateControl, VideoBitDepth, } from "./types.js"; +import { userTable } from "../user/user.sql.js"; export const sessionTable = pgTable("sessions", { ...id, ...timestamps, description: text("description"), + ownerID: ulid("owner_id") + .references(() => userTable.id, { + onDelete: "cascade", + }) + .notNull(), retry: jsonb("retry").$type(), queue: jsonb("queue").$type(), machine: varchar("machine", { length: 200 }).$type(), diff --git a/packages/function/src/api/routes/session.ts b/packages/function/src/api/routes/session.ts index 4066993c..4068d60d 100644 --- a/packages/function/src/api/routes/session.ts +++ b/packages/function/src/api/routes/session.ts @@ -2,33 +2,66 @@ import { Hono } from "hono"; import { notPublic } from "../utils/auth.js"; import { ErrorResponses } from "../utils/error.js"; import { Session } from "@nestri/core/session/index"; -import { describeRoute, resolver } from "hono-openapi"; +import { describeRoute, resolver, validator } from "hono-openapi"; export namespace SessionRoute { - export const route = new Hono().use(notPublic).get( - "/", - describeRoute({ - tags: ["Session"], - operationId: "session.list", - summary: "List all active sessions", - description: "List all the current user's active sessions", - responses: { - 200: { - description: "List of sessions", - content: { - "application/json": { - schema: resolver(Session.Info.array()), + export const route = new Hono() + .use(notPublic) + .get( + "/", + describeRoute({ + tags: ["Session"], + operationId: "session.list", + summary: "List all sessions", + description: "List all the current user's sessions", + responses: { + 200: { + description: "List of sessions", + content: { + "application/json": { + schema: resolver(Session.Info.array()), + }, }, }, + 401: ErrorResponses[401], + 403: ErrorResponses[403], + 429: ErrorResponses[429], }, - 401: ErrorResponses[401], - 403: ErrorResponses[403], - 429: ErrorResponses[429], + }), + async (c) => { + const sessions = await Session.list(); + return c.json(sessions); }, - }), - async (c) => { - const sessions = await Session.list(); - return c.json(sessions); - }, - ); + ) + .post( + "/", + describeRoute({ + tags: ["Session"], + operationId: "session.create", + summary: "Create a new session", + description: "Create a new session for the current user", + responses: { + 201: { + description: "The created session", + content: { + "application/json": { + schema: resolver(Session.Info), + }, + }, + }, + 400: ErrorResponses[400], + 401: ErrorResponses[401], + 403: ErrorResponses[403], + 429: ErrorResponses[429], + }, + }), + validator("json", Session.Info.omit({ id: true })), + async (c) => { + const sessionData = c.req.valid("json"); + + const id = await Session.create(sessionData); + + return c.json({ ...sessionData, id }, 201); + }, + ); }