feat: scaffold Hono server with auth and health check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "droidclaw",
|
"name": "droidclaw",
|
||||||
|
"workspaces": ["packages/*", "server"],
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "AI agent that takes control of your Android phone — give it a goal, it figures out the taps",
|
"description": "AI agent that takes control of your Android phone — give it a goal, it figures out the taps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -13,12 +14,14 @@
|
|||||||
"@openrouter/ai-sdk-provider": "^2.1.1",
|
"@openrouter/ai-sdk-provider": "^2.1.1",
|
||||||
"ai": "^6.0.72",
|
"ai": "^6.0.72",
|
||||||
"fast-xml-parser": "^4.5.0",
|
"fast-xml-parser": "^4.5.0",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
"openai": "^4.73.0",
|
"openai": "^4.73.0",
|
||||||
"yaml": "^2.8.2",
|
"yaml": "^2.8.2",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.1.0",
|
"@types/bun": "^1.1.0",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"typescript": "^5.6.0"
|
"typescript": "^5.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
server/.env.example
Normal file
3
server/.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DATABASE_URL="postgres://user:password@host:port/db-name"
|
||||||
|
PORT=8080
|
||||||
|
CORS_ORIGIN="http://localhost:5173"
|
||||||
12
server/Dockerfile
Normal file
12
server/Dockerfile
Normal file
@@ -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"]
|
||||||
21
server/package.json
Normal file
21
server/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
server/src/auth.ts
Normal file
11
server/src/auth.ts
Normal file
@@ -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()],
|
||||||
|
});
|
||||||
6
server/src/db.ts
Normal file
6
server/src/db.ts
Normal file
@@ -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);
|
||||||
9
server/src/env.ts
Normal file
9
server/src/env.ts
Normal file
@@ -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");
|
||||||
|
}
|
||||||
44
server/src/index.ts
Normal file
44
server/src/index.ts
Normal file
@@ -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}`);
|
||||||
17
server/tsconfig.json
Normal file
17
server/tsconfig.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user