Skip to content

Commit c5a5700

Browse files
committed
Cleanup
1 parent 6a75755 commit c5a5700

14 files changed

Lines changed: 351 additions & 244 deletions

File tree

README.md

Lines changed: 96 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,52 @@
11
# OpenWorkers API (Hono + Bun)
22

3-
Rewrite of dash-api using Hono framework and Bun runtime.
3+
Lightweight REST API for OpenWorkers platform using Hono framework and Bun runtime.
44

55
## Features
66

77
- **Hono** - Ultra-fast web framework
88
- **Bun native Postgres** - `Bun.sql()` for database access
9+
- **Zod validation** - Type-safe input/output validation
910
- **Isolated services** - Easy to swap Postgres with gateway later
1011
- **Pure REST** - No GraphQL complexity
1112
- **JWT auth** - Using `hono/jwt`
13+
- **Standalone binary** - Compile to single executable
1214

1315
## Structure
1416

1517
```
1618
src/
1719
├── services/
18-
│ ├── db.ts - Database abstraction (Bun Postgres or Gateway)
19-
│ └── workers.ts - Workers business logic
20+
│ ├── db/
21+
│ │ ├── client.ts - Bun Postgres client
22+
│ │ ├── users.ts - User DB queries
23+
│ │ ├── workers.ts - Worker DB queries
24+
│ │ ├── crons.ts - Cron DB queries
25+
│ │ ├── environments.ts - Environment DB queries
26+
│ │ └── domains.ts - Domain DB queries
27+
│ ├── auth.ts - Authentication logic
28+
│ ├── workers.ts - Workers business logic
29+
│ ├── crons.ts - Crons business logic
30+
│ ├── environments.ts - Environments business logic
31+
│ └── domains.ts - Domains business logic
2032
├── routes/
21-
│ └── workers.ts - Workers REST endpoints
33+
│ ├── auth.ts - Auth endpoints
34+
│ ├── users.ts - User endpoints
35+
│ ├── workers.ts - Workers endpoints
36+
│ ├── crons.ts - Crons endpoints
37+
│ ├── environments.ts - Environments endpoints
38+
│ └── domains.ts - Domains endpoints
2239
├── middlewares/
23-
│ └── auth.ts - JWT authentication
40+
│ └── auth.ts - JWT middleware
2441
├── types/
25-
│ └── index.ts - TypeScript types
26-
└── index.ts - Main app
42+
│ ├── schemas/ - Zod validation schemas
43+
│ ├── validators.ts - Schema validators
44+
│ └── index.ts - Type exports
45+
├── utils/
46+
│ └── validate.ts - Response validation helpers
47+
├── config/
48+
│ └── index.ts - Configuration
49+
└── index.ts - Main app
2750
```
2851

2952
## Quick Start
@@ -39,76 +62,109 @@ cp .env.example .env
3962
# Run development server (hot reload)
4063
bun run dev
4164

42-
# Production
65+
# Production (run with Bun)
4366
bun run start
67+
68+
# Compile standalone binary
69+
bun run compile
70+
# → Creates dist/openworkers-api (executable)
4471
```
4572

4673
## API Endpoints
4774

4875
### Auth (Public)
4976

50-
- `GET /login/github` - Redirect to GitHub OAuth
51-
- `GET /callback/github` - GitHub OAuth callback (creates JWT, redirects to dashboard)
52-
- `POST /refresh` - Refresh access token using refresh token
77+
- `POST /api/v1/openid/github` - Initiate GitHub OAuth flow
78+
- `GET /api/v1/callback/github` - GitHub OAuth callback (creates JWT)
79+
- `POST /api/v1/refresh` - Refresh access token using refresh token
80+
81+
### Users (Protected)
82+
83+
- `GET /api/v1/profile` - Get current user profile
5384

5485
### Workers (Protected)
5586

56-
- `GET /api/workers` - List all workers
57-
- `GET /api/workers/:id` - Get single worker
58-
- `POST /api/workers` - Create worker
59-
- `PUT /api/workers/:id` - Update worker
60-
- `DELETE /api/workers/:id` - Delete worker
87+
- `GET /api/v1/workers` - List all workers
88+
- `GET /api/v1/workers/name-exists/:name` - Check if worker name exists
89+
- `GET /api/v1/workers/:id` - Get single worker
90+
- `POST /api/v1/workers` - Create worker
91+
- `PUT /api/v1/workers/:id` - Update worker
92+
- `POST /api/v1/workers/:id/crons` - Create cron for worker
93+
- `DELETE /api/v1/workers/:id` - Delete worker
94+
95+
### Crons (Protected)
96+
97+
- `POST /api/v1/crons` - Create cron
98+
- `PUT /api/v1/crons/:id` - Update cron
99+
- `DELETE /api/v1/crons/:id` - Delete cron
100+
101+
### Environments (Protected)
102+
103+
- `GET /api/v1/environments` - List all environments
104+
- `GET /api/v1/environments/:id` - Get environment by ID (includes values)
105+
- `POST /api/v1/environments` - Create environment
106+
- `PUT /api/v1/environments/:id` - Update environment (name/desc and/or values)
107+
- `DELETE /api/v1/environments/:id` - Delete environment
108+
109+
### Domains (Protected)
110+
111+
- `GET /api/v1/domains` - List all domains
112+
- `POST /api/v1/domains` - Create domain
113+
- `DELETE /api/v1/domains/:name` - Delete domain
61114

62115
### Authentication
63116

64-
All `/api/*` endpoints require JWT Bearer token in `Authorization` header:
117+
All `/api/v1/*` endpoints (except auth endpoints) require JWT Bearer token in `Authorization` header:
65118

66119
```
67120
Authorization: Bearer <token>
68121
```
69122

70123
OAuth flow:
71-
1. User clicks "Login with GitHub" → `GET /login/github`
72-
2. GitHub redirects to → `GET /callback/github?code=...`
124+
125+
1. User initiates login → `POST /api/v1/openid/github`
126+
2. GitHub redirects to → `GET /api/v1/callback/github?code=...`
73127
3. Server exchanges code for user profile
74128
4. Creates/finds user in DB
75129
5. Issues JWT tokens (access + refresh)
76-
6. Redirects to dashboard with token
130+
6. Returns tokens in response body + sets cookie
77131

78132
## Database Service Abstraction
79133

80134
The `db.ts` service can be swapped between implementations:
81135

82136
**Current**: Bun native Postgres
137+
83138
```typescript
84-
import { sql } from 'bun';
139+
import { sql } from "bun";
85140
```
86141

87-
**Future**: Postgres Gateway
142+
**Future**: Postgres Gateway (HTTP-based)
143+
88144
```typescript
89145
fetch('http://postgres-gateway:8080/query', ...)
90146
```
91147

92-
Change only happens in `src/services/db.ts` - all other code stays the same.
148+
Change only happens in `src/services/db/` - all other code stays the same.
93149

94-
## TODO
150+
## Status
95151

96-
- [ ] Auth routes (login, register, refresh)
97-
- [ ] Users routes
98-
- [ ] Environments routes
99-
- [ ] Domains routes
100-
- [ ] Crons routes
101-
- [ ] Server-Sent Events for logs
102-
- [ ] Input validation
103-
- [ ] Error handling middleware
104-
- [ ] Tests
152+
### ✅ Implemented
105153

106-
## Migration from NestJS
154+
- [x] Auth routes (GitHub OAuth, refresh)
155+
- [x] Users routes (profile)
156+
- [x] Workers routes (CRUD + name uniqueness check)
157+
- [x] Environments routes (CRUD + values management)
158+
- [x] Domains routes (CRUD)
159+
- [x] Crons routes (CRUD)
160+
- [x] Zod input/output validation
161+
- [x] JWT authentication middleware
162+
- [x] Standalone binary compilation
107163

108-
This replaces `openworkers-dash/dash-api` with simpler implementation:
164+
### 🚧 TODO
109165

110-
- ✅ No NestJS boilerplate
111-
- ~90% less code
112-
- ✅ Faster startup (<50ms vs ~2s)
113-
- ✅ Native Bun performance
114-
- ✅ Easier to audit (~500 lines vs ~5000 lines)
166+
- [ ] Error handling middleware (standardized error responses)
167+
- [ ] Request validation middleware
168+
- [ ] Rate limiting
169+
- [ ] Tests (unit + integration)
170+
- [ ] API documentation (OpenAPI/Swagger)

examples/default-worker.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
addEventListener("fetch", (event: FetchEvent) => {
2+
event.respondWith(
3+
handleRequest(event.request).catch(
4+
(err: Error) => new Response(err.stack, { status: 500 })
5+
)
6+
);
7+
});
8+
9+
addEventListener("scheduled", (event: ScheduledEvent) => {
10+
event.waitUntil(handleSchedule(event.scheduledTime));
11+
});
12+
13+
async function handleSchedule(scheduledTime: number): Promise<void> {
14+
console.log(`Scheduled event at ${new Date(scheduledTime).toISOString()}`);
15+
}
16+
17+
// More examples available at: https://openworkers.com/docs
18+
async function handleRequest(request: Request): Promise<Response> {
19+
const { pathname } = new URL(request.url);
20+
21+
// Return a 404 response for requests to /favicon.ico.
22+
if (pathname.startsWith("/favicon.ico")) {
23+
return new Response("Not found", { status: 404 });
24+
}
25+
26+
// Return a JSON response for requests to /api containing the requested pathname.
27+
if (pathname.startsWith("/api")) {
28+
return new Response(JSON.stringify({ pathname }), {
29+
headers: { "Content-Type": "application/json" },
30+
});
31+
}
32+
33+
// Return a HTML response for all other requests.
34+
return new Response("<h3>Hello world!</h3>", {
35+
headers: { "Content-Type": "text/html" },
36+
});
37+
}

src/routes/auth.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ auth.get("/callback/github", async (c) => {
7878
avatar_url: string;
7979
};
8080

81-
console.log("GitHub user:", githubUser);
82-
8381
// Find or create user in our DB
8482
const user = await authService.findOrCreateGitHubUser(githubUser);
8583

src/routes/crons.ts

Lines changed: 68 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,88 @@
1-
import { Hono } from 'hono';
2-
import { cronsService } from '../services/crons';
3-
import { workersService } from '../services/workers';
4-
import { CronCreateInputSchema, CronUpdateInputSchema, CronSchema } from '../types';
5-
import { jsonResponse } from '../utils/validate';
1+
import { Hono } from "hono";
2+
import { cronsService } from "../services/crons";
3+
import { workersService } from "../services/workers";
4+
import {
5+
CronCreateInputSchema,
6+
CronUpdateInputSchema,
7+
CronSchema,
8+
WorkerSchema,
9+
} from "../types";
10+
import { jsonResponse } from "../utils/validate";
611

712
const crons = new Hono();
813

914
// PUT /crons/:id - Update cron
10-
crons.put('/:id', async (c) => {
11-
const userId = c.get('userId');
12-
const id = c.req.param('id');
13-
const body = await c.req.json();
15+
crons.put("/:id", async (c) => {
16+
const userId = c.get("userId");
17+
const id = c.req.param("id");
18+
const body = await c.req.json();
1419

15-
try {
16-
const payload = CronUpdateInputSchema.parse(body);
17-
const cron = await cronsService.update(userId, id, payload);
20+
try {
21+
const payload = CronUpdateInputSchema.parse(body);
22+
const cron = await cronsService.update(userId, id, payload);
1823

19-
return jsonResponse(c, CronSchema, cron);
20-
} catch (error) {
21-
console.error('Failed to update cron:', error);
22-
return c.json({
23-
error: 'Failed to update cron',
24-
message: error instanceof Error ? error.message : 'Unknown error'
25-
}, 500);
26-
}
24+
// Return updated worker
25+
const updatedWorker = await workersService.findById(userId, cron.workerId);
26+
27+
return jsonResponse(c, WorkerSchema, updatedWorker);
28+
} catch (error) {
29+
console.error("Failed to update cron:", error);
30+
return c.json(
31+
{
32+
error: "Failed to update cron",
33+
message: error instanceof Error ? error.message : "Unknown error",
34+
},
35+
500
36+
);
37+
}
2738
});
2839

2940
// DELETE /crons/:id - Delete cron
30-
crons.delete('/:id', async (c) => {
31-
const userId = c.get('userId');
32-
const id = c.req.param('id');
33-
34-
try {
35-
// Get cron first to know which worker it belongs to
36-
const cron = await cronsService.findById(userId, id);
37-
if (!cron) {
38-
return c.json({ error: 'Cron not found' }, 404);
39-
}
41+
crons.delete("/:id", async (c) => {
42+
const userId = c.get("userId");
43+
const id = c.req.param("id");
4044

41-
const deleted = await cronsService.delete(userId, id);
45+
try {
46+
// Get cron first to know which worker it belongs to
47+
const cron = await cronsService.findById(userId, id);
48+
if (!cron) {
49+
return c.json({ error: "Cron not found" }, 404);
50+
}
4251

43-
if (deleted === 0) {
44-
return c.json({ error: 'Cron not found' }, 404);
45-
}
52+
const deleted = await cronsService.delete(userId, id);
4653

47-
// Return updated worker
48-
const updatedWorker = await workersService.findById(userId, cron.workerId);
49-
return c.json(updatedWorker);
50-
} catch (error) {
51-
console.error('Failed to delete cron:', error);
52-
return c.json({ error: 'Failed to delete cron' }, 500);
54+
if (deleted === 0) {
55+
return c.json({ error: "Cron not found" }, 404);
5356
}
57+
58+
// Return updated worker
59+
const updatedWorker = await workersService.findById(userId, cron.workerId);
60+
return c.json(updatedWorker);
61+
} catch (error) {
62+
console.error("Failed to delete cron:", error);
63+
return c.json({ error: "Failed to delete cron" }, 500);
64+
}
5465
});
5566

5667
// POST /crons - Create cron
57-
crons.post('/', async (c) => {
58-
const userId = c.get('userId');
59-
const body = await c.req.json();
68+
crons.post("/", async (c) => {
69+
const userId = c.get("userId");
70+
const body = await c.req.json();
6071

61-
try {
62-
const payload = CronCreateInputSchema.parse(body);
63-
const cron = await cronsService.create(userId, payload);
64-
return jsonResponse(c, CronSchema, cron, 201);
65-
} catch (error) {
66-
console.error('Failed to create cron:', error);
67-
return c.json({
68-
error: 'Failed to create cron',
69-
message: error instanceof Error ? error.message : 'Unknown error'
70-
}, 500);
71-
}
72+
try {
73+
const payload = CronCreateInputSchema.parse(body);
74+
const cron = await cronsService.create(userId, payload);
75+
return jsonResponse(c, CronSchema, cron, 201);
76+
} catch (error) {
77+
console.error("Failed to create cron:", error);
78+
return c.json(
79+
{
80+
error: "Failed to create cron",
81+
message: error instanceof Error ? error.message : "Unknown error",
82+
},
83+
500
84+
);
85+
}
7286
});
7387

7488
export default crons;

0 commit comments

Comments
 (0)