feat: add REST routes for devices, goals, and health
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,9 @@ import {
|
|||||||
handleDashboardClose,
|
handleDashboardClose,
|
||||||
} from "./ws/dashboard.js";
|
} from "./ws/dashboard.js";
|
||||||
import type { WebSocketData } from "./ws/sessions.js";
|
import type { WebSocketData } from "./ws/sessions.js";
|
||||||
|
import { devices } from "./routes/devices.js";
|
||||||
|
import { goals } from "./routes/goals.js";
|
||||||
|
import { health } from "./routes/health.js";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
@@ -27,8 +30,10 @@ app.on(["POST", "GET"], "/api/auth/*", (c) => {
|
|||||||
return auth.handler(c.req.raw);
|
return auth.handler(c.req.raw);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Health check
|
// REST routes
|
||||||
app.get("/health", (c) => c.json({ status: "ok" }));
|
app.route("/devices", devices);
|
||||||
|
app.route("/goals", goals);
|
||||||
|
app.route("/health", health);
|
||||||
|
|
||||||
// Start server with WebSocket support
|
// Start server with WebSocket support
|
||||||
const server = Bun.serve<WebSocketData>({
|
const server = Bun.serve<WebSocketData>({
|
||||||
|
|||||||
24
server/src/middleware/auth.ts
Normal file
24
server/src/middleware/auth.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Context, Next } from "hono";
|
||||||
|
import { auth } from "../auth.js";
|
||||||
|
|
||||||
|
/** Hono Env type for routes protected by sessionMiddleware */
|
||||||
|
export type AuthEnv = {
|
||||||
|
Variables: {
|
||||||
|
user: { id: string; name: string; email: string; [key: string]: unknown };
|
||||||
|
session: { id: string; userId: string; [key: string]: unknown };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function sessionMiddleware(c: Context, next: Next) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: c.req.raw.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return c.json({ error: "unauthorized" }, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.set("user", session.user);
|
||||||
|
c.set("session", session.session);
|
||||||
|
await next();
|
||||||
|
}
|
||||||
22
server/src/routes/devices.ts
Normal file
22
server/src/routes/devices.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import { sessionMiddleware, type AuthEnv } from "../middleware/auth.js";
|
||||||
|
import { sessions } from "../ws/sessions.js";
|
||||||
|
|
||||||
|
const devices = new Hono<AuthEnv>();
|
||||||
|
devices.use("*", sessionMiddleware);
|
||||||
|
|
||||||
|
devices.get("/", (c) => {
|
||||||
|
const user = c.get("user");
|
||||||
|
const userDevices = sessions.getDevicesForUser(user.id);
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
userDevices.map((d) => ({
|
||||||
|
deviceId: d.deviceId,
|
||||||
|
name: d.deviceInfo?.model ?? "Unknown Device",
|
||||||
|
deviceInfo: d.deviceInfo,
|
||||||
|
connectedAt: d.connectedAt.toISOString(),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { devices };
|
||||||
36
server/src/routes/goals.ts
Normal file
36
server/src/routes/goals.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import { sessionMiddleware, type AuthEnv } from "../middleware/auth.js";
|
||||||
|
import { sessions } from "../ws/sessions.js";
|
||||||
|
|
||||||
|
const goals = new Hono<AuthEnv>();
|
||||||
|
goals.use("*", sessionMiddleware);
|
||||||
|
|
||||||
|
goals.post("/", async (c) => {
|
||||||
|
const user = c.get("user");
|
||||||
|
const body = await c.req.json<{ deviceId: string; goal: string }>();
|
||||||
|
|
||||||
|
if (!body.deviceId || !body.goal) {
|
||||||
|
return c.json({ error: "deviceId and goal are required" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = sessions.getDevice(body.deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return c.json({ error: "device not connected" }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.userId !== user.id) {
|
||||||
|
return c.json({ error: "device does not belong to you" }, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO (Task 6): start agent loop for this device+goal
|
||||||
|
const sessionId = crypto.randomUUID();
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
sessionId,
|
||||||
|
deviceId: body.deviceId,
|
||||||
|
goal: body.goal,
|
||||||
|
status: "queued",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export { goals };
|
||||||
13
server/src/routes/health.ts
Normal file
13
server/src/routes/health.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import { sessions } from "../ws/sessions.js";
|
||||||
|
|
||||||
|
const health = new Hono();
|
||||||
|
|
||||||
|
health.get("/", (c) => {
|
||||||
|
return c.json({
|
||||||
|
status: "ok",
|
||||||
|
connectedDevices: sessions.getStats().devices,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export { health };
|
||||||
Reference in New Issue
Block a user