From bc014fd587bf12176c0343910b1c7dcf49dcb7a9 Mon Sep 17 00:00:00 2001 From: Sanju Sivalingam Date: Tue, 17 Feb 2026 14:07:19 +0530 Subject: [PATCH] feat: scaffold Hono server with auth and health check Co-Authored-By: Claude Opus 4.6 --- package.json | 3 +++ server/.env.example | 3 +++ server/Dockerfile | 12 ++++++++++++ server/package.json | 21 +++++++++++++++++++++ server/src/auth.ts | 11 +++++++++++ server/src/db.ts | 6 ++++++ server/src/env.ts | 9 +++++++++ server/src/index.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ server/tsconfig.json | 17 +++++++++++++++++ 9 files changed, 126 insertions(+) create mode 100644 server/.env.example create mode 100644 server/Dockerfile create mode 100644 server/package.json create mode 100644 server/src/auth.ts create mode 100644 server/src/db.ts create mode 100644 server/src/env.ts create mode 100644 server/src/index.ts create mode 100644 server/tsconfig.json diff --git a/package.json b/package.json index 14ed2fe..a647183 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "droidclaw", + "workspaces": ["packages/*", "server"], "version": "1.0.0", "description": "AI agent that takes control of your Android phone — give it a goal, it figures out the taps", "type": "module", @@ -13,12 +14,14 @@ "@openrouter/ai-sdk-provider": "^2.1.1", "ai": "^6.0.72", "fast-xml-parser": "^4.5.0", + "js-yaml": "^4.1.1", "openai": "^4.73.0", "yaml": "^2.8.2", "zod": "^4.3.6" }, "devDependencies": { "@types/bun": "^1.1.0", + "@types/js-yaml": "^4.0.9", "typescript": "^5.6.0" } } diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 0000000..0729e94 --- /dev/null +++ b/server/.env.example @@ -0,0 +1,3 @@ +DATABASE_URL="postgres://user:password@host:port/db-name" +PORT=8080 +CORS_ORIGIN="http://localhost:5173" diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..d31eca0 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,12 @@ +FROM oven/bun:1 + +WORKDIR /app + +COPY packages/shared ./packages/shared +COPY server ./server + +WORKDIR /app/server +RUN bun install + +EXPOSE 8080 +CMD ["bun", "src/index.ts"] diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..228e87b --- /dev/null +++ b/server/package.json @@ -0,0 +1,21 @@ +{ + "name": "@droidclaw/server", + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "bun --watch src/index.ts", + "start": "bun src/index.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@droidclaw/shared": "workspace:*", + "hono": "^4.7.0", + "better-auth": "^1.3.27", + "drizzle-orm": "^0.44.5", + "postgres": "^3.4.7" + }, + "devDependencies": { + "@types/bun": "^1.1.0", + "typescript": "^5.9.2" + } +} diff --git a/server/src/auth.ts b/server/src/auth.ts new file mode 100644 index 0000000..ec1a0b4 --- /dev/null +++ b/server/src/auth.ts @@ -0,0 +1,11 @@ +import { betterAuth } from "better-auth"; +import { apiKey } from "better-auth/plugins"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { db } from "./db.js"; + +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: "pg", + }), + plugins: [apiKey()], +}); diff --git a/server/src/db.ts b/server/src/db.ts new file mode 100644 index 0000000..8d0efb6 --- /dev/null +++ b/server/src/db.ts @@ -0,0 +1,6 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; +import { env } from "./env.js"; + +const client = postgres(env.DATABASE_URL); +export const db = drizzle(client); diff --git a/server/src/env.ts b/server/src/env.ts new file mode 100644 index 0000000..6449730 --- /dev/null +++ b/server/src/env.ts @@ -0,0 +1,9 @@ +export const env = { + DATABASE_URL: process.env.DATABASE_URL!, + PORT: parseInt(process.env.PORT || "8080"), + CORS_ORIGIN: process.env.CORS_ORIGIN || "http://localhost:5173", +}; + +if (!env.DATABASE_URL) { + throw new Error("DATABASE_URL is not set"); +} diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..1912e40 --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,44 @@ +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { auth } from "./auth.js"; +import { env } from "./env.js"; + +const app = new Hono(); + +// CORS for dashboard +app.use( + "*", + cors({ + origin: env.CORS_ORIGIN, + allowHeaders: ["Content-Type", "Authorization"], + allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + credentials: true, + }) +); + +// Better Auth handler +app.on(["POST", "GET"], "/api/auth/*", (c) => { + return auth.handler(c.req.raw); +}); + +// Health check +app.get("/health", (c) => c.json({ status: "ok" })); + +// Start server with WebSocket support +const server = Bun.serve({ + port: env.PORT, + fetch: app.fetch, + websocket: { + open(ws) { + console.log("WebSocket connected"); + }, + message(ws, message) { + // placeholder — Task 4 implements device/dashboard handlers + }, + close(ws) { + console.log("WebSocket disconnected"); + }, + }, +}); + +console.log(`Server running on port ${server.port}`); diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..ab48925 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "types": ["bun"], + "paths": { + "@droidclaw/shared": ["../packages/shared/src"] + } + }, + "include": ["src/**/*.ts"] +}