Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d28903c611 | |||
| a4f3ea7837 | |||
| 7c4ef8a51c | |||
| 2eab4b92cc | |||
| 51502055db | |||
| 553d9647c2 | |||
| b954ce5f72 |
+3
-4
@@ -1,6 +1,5 @@
|
||||
# Core Server Configuration
|
||||
PORT=3000
|
||||
APP_PORT=3000
|
||||
API_KEY=your_secret_api_key_here
|
||||
|
||||
# Redis Configuration (Use redis://redis:6379 for Docker)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
+4
-1
@@ -11,4 +11,7 @@ ENV NODE_ENV=production
|
||||
USER bun
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["bun", "run", "src/index.ts"]
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD bun -e "fetch('http://localhost:3000/health').then(res => res.ok ? process.exit(0) : process.exit(1)).catch(e => process.exit(1))"
|
||||
|
||||
CMD ["bun", "run", "start"]
|
||||
|
||||
@@ -20,47 +20,63 @@
|
||||
- **Styling**: Tailwind CSS & DaisyUI
|
||||
- **Schema & Validation**: Zod
|
||||
|
||||
## Quick Start
|
||||
## Getting Started
|
||||
|
||||
toknd can be deployed either as a containerized service or self-hosted directly on your hardware.
|
||||
|
||||
### 1. Environment Setup
|
||||
Clone the repository and create your environment file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
Ensure you define a strong `API_KEY` in your `.env`.
|
||||
Define a strong `API_KEY` and ensure `REDIS_URL` points to a valid Redis instance.
|
||||
|
||||
### 2. Local Development (with Auto-Watch)
|
||||
We use a Docker Compose override system to enable hot-reloading locally:
|
||||
```bash
|
||||
podman compose up --build
|
||||
```
|
||||
*Note: This mounts your ./src directory into the container and uses bun --hot to restart on any code changes.*
|
||||
### 2. Choose Deployment Method
|
||||
|
||||
### 3. Production Deployment
|
||||
For production, only the core docker-compose.yml is used:
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
#### Option A: Containerized (Recommended)
|
||||
This is the easiest way to get up and running, as it bundles the application and a Redis instance together.
|
||||
|
||||
- **Development (with Hot-Reload)**:
|
||||
```bash
|
||||
podman compose up --build
|
||||
```
|
||||
- **Production**:
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
#### Option B: Self-Hosting (Bare Metal)
|
||||
Ideal for lightweight deployments or custom environments where you already have Bun and Redis.
|
||||
|
||||
1. **Install Dependencies**:
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
2. **Start the Server**:
|
||||
- **Development**: `bun run dev` (with hot-reload)
|
||||
- **Production**: `bun run start`
|
||||
*Note: Ensure your Redis server is running and accessible via the `REDIS_URL` in your `.env`.*
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
All protected endpoints require an Authorization header:
|
||||
toknd provides a built-in **Scalar API Reference** that allows you to explore and test all endpoints directly from your browser.
|
||||
|
||||
- **Interactive UI**: [http://localhost:3000/api](http://localhost:3000/api) (or `/docs`)
|
||||
- **OpenAPI Spec (JSON)**: [http://localhost:3000/doc](http://localhost:3000/doc)
|
||||
|
||||
All protected endpoints require a Bearer token in the `Authorization` header:
|
||||
`Authorization: Bearer <your_master_api_key>`
|
||||
|
||||
### Token Brokerage
|
||||
- **Get Valid Token**: `GET /api/token/:provider`
|
||||
- Returns a valid access token. Automatically triggers a refresh if the current one is expired.
|
||||
- **Registry Status**: `GET /api/status`
|
||||
- Returns the connectivity and refresh status of all configured providers.
|
||||
|
||||
### Authentication Flow
|
||||
1. **Initiate**: `GET /auth/:provider/login`
|
||||
2. **Callback**: `GET /auth/callback` (Handled internally by toknd)
|
||||
### Core Concepts
|
||||
- **Token Brokerage**: Automated access token retrieval and background refreshes for all configured providers.
|
||||
- **Provider Management**: Register and manage OAuth2 providers via the Dashboard or the configuration API.
|
||||
|
||||
## Dashboard
|
||||
Access the toknd dashboard at:
|
||||
Access the **toknd** dashboard at:
|
||||
`http://localhost:3000/app`
|
||||
|
||||
Authenticate the registry using your Master API Key to manage your providers and view live token status.
|
||||
The dashboard allows you to manage provider configurations, view live token statuses, and manually trigger refreshes. Authenticate using your **Master API Key**.
|
||||
|
||||
---
|
||||
|
||||
+5
-2
@@ -3,9 +3,10 @@ services:
|
||||
build: .
|
||||
restart: always
|
||||
ports:
|
||||
- "${PORT:-3000}:3000"
|
||||
- "${APP_PORT:-3000}:3000"
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- API_KEY=${API_KEY}
|
||||
depends_on:
|
||||
- redis
|
||||
@@ -13,6 +14,8 @@ services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: always
|
||||
ports:
|
||||
- "${REDIS_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run --hot src/index.ts",
|
||||
"start": "bun src/index.ts",
|
||||
"test": "bun test",
|
||||
"lint": "bunx @biomejs/biome check src",
|
||||
"check-all": "bunx @biomejs/biome check .",
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const configSchema = z.object({
|
||||
PORT: z.string().default("3000"),
|
||||
REDIS_URL: z.string(),
|
||||
APP_PORT: z.string().default("3000"),
|
||||
REDIS_HOST: z.string().default("redis"),
|
||||
REDIS_PORT: z.coerce.number().default(6379),
|
||||
API_KEY: z.string(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Redis } from "ioredis";
|
||||
import { config } from "../config";
|
||||
|
||||
export const redis = new Redis(config.REDIS_URL);
|
||||
export const redis = new Redis({
|
||||
host: config.REDIS_HOST,
|
||||
port: config.REDIS_PORT,
|
||||
});
|
||||
|
||||
+8
-2
@@ -3,6 +3,7 @@ import { Scalar } from "@scalar/hono-api-reference";
|
||||
import { logger } from "hono/logger";
|
||||
import { prettyJSON } from "hono/pretty-json";
|
||||
import { config } from "./config";
|
||||
import { redis } from "./core/RedisClient";
|
||||
import { apiRoutes } from "./routes/api";
|
||||
import { authRoutes } from "./routes/auth";
|
||||
import { configRoutes } from "./routes/config";
|
||||
@@ -63,11 +64,16 @@ app.onError((err, c) => {
|
||||
return c.json({ error: "Internal Server Error", message: err.message }, 500);
|
||||
});
|
||||
|
||||
app.get("/health", (c) => c.json({ status: "ok" }));
|
||||
app.get("/health", async (c) => {
|
||||
if (redis.status !== "ready") {
|
||||
return c.json({ status: "error", message: "Redis down", redis: redis.status }, 503);
|
||||
}
|
||||
return c.json({ status: "ok" });
|
||||
});
|
||||
|
||||
export { app };
|
||||
|
||||
export default {
|
||||
port: Number.parseInt(config.PORT, 10),
|
||||
port: Number.parseInt(config.APP_PORT, 10),
|
||||
fetch: app.fetch,
|
||||
};
|
||||
|
||||
@@ -35,6 +35,17 @@ describe("API Integration", () => {
|
||||
expect(body.status).toBe("ok");
|
||||
});
|
||||
|
||||
it("should return 503 for health check if redis is down", async () => {
|
||||
redis.status = "connecting";
|
||||
|
||||
const res = await app.request("/health");
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toBe(503);
|
||||
expect(body.status).toBe("error");
|
||||
expect(body.redis).toBe("connecting");
|
||||
});
|
||||
|
||||
it("should return 200 for status with valid API Key", async () => {
|
||||
redis.keys.mockReturnValue(Promise.resolve(["config:trakt"]));
|
||||
redis.get.mockImplementation((key) => {
|
||||
|
||||
+5
-2
@@ -2,14 +2,17 @@ import { mock } from "bun:test";
|
||||
|
||||
// Global test setup to stub environment variables
|
||||
process.env.API_KEY = "test-api-key";
|
||||
process.env.REDIS_URL = "redis://localhost:6379";
|
||||
process.env.PORT = "3000";
|
||||
process.env.REDIS_HOST = "localhost";
|
||||
process.env.REDIS_PORT = "6379";
|
||||
process.env.APP_PORT = "3000";
|
||||
|
||||
// Global Redis mock
|
||||
mock.module("../src/core/RedisClient", () => ({
|
||||
redis: {
|
||||
status: "ready",
|
||||
get: mock(() => Promise.resolve(null)),
|
||||
set: mock(() => Promise.resolve()),
|
||||
keys: mock(() => Promise.resolve([])),
|
||||
on: mock(() => {}),
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user