Skip to content

Commit

Permalink
refactor: refactoring app enter
Browse files Browse the repository at this point in the history
  • Loading branch information
C-Dao committed Apr 2, 2023
1 parent 0118e04 commit fbf6a73
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 71 deletions.
1 change: 1 addition & 0 deletions .env.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OPENAI_DOMAIN="https://api.openai.com"
46 changes: 30 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,21 @@
</a>
</p>

This is a open source implementation of [OpenCat for Team](https://opencat.app/) Backend for edge platforms.

Supported platforms:

- Cloudflare Workers
- Deno
- Deno Deploy *(requires kv beta access)*

This project uses Cloudflare KV or Deno KV as backend database.
This is a open source implementation of [OpenCat for Team](https://opencat.app/) backend for edge platforms.

## Run locally with Deno
Supported platforms:

You need to have Deno >= 1.32 installed.
- [Cloudflare Workers](#deploy-to-cloudflare-workers)
- [Deno](#run-locally-with-Deno)
- [Deno Deploy]() *(requires kv beta access)*

```sh
deno run -A --unstable deno/index.ts
```
This project uses Cloudflare KV or Deno KV as backend database.

## Deploy to Cloudflare Workers
>Before you begin, you need to have a Cloudflare account and be able to use Cloudflare Worker. Have a joy!
## Deploy to Cloudflare Workers With Wrangler
>Before you begin, you need to have a [Cloudflare](https://www.cloudflare.com/) account and be able to use [Cloudflare Workers](https://www.cloudflare.com/zh-cn/products/workers/). Have a joy!
### 1. Git clone the repo and enter repo
```sh
cd ./opencatd_worker
Expand All @@ -34,9 +29,9 @@ You need to have Deno >= 1.32 installed.
```sh
yarn
```
### 3. Copy `wrangler.bak.toml` to `wrangler.toml`
### 3. Copy `wrangler.toml.bak` to `wrangler.toml`
```sh
cp wrangler.bak.toml wrangler.toml
cp wrangler.toml.bak wrangler.toml
```
### 4. Create Cloudflare KV Namespace
```sh
Expand All @@ -58,7 +53,26 @@ You need to have Deno >= 1.32 installed.
yarn deploy
```

## Run locally with Deno
>You need to have Deno >= 1.32 installed.
### 1. Install Deno
MacOS user can use under command line to install deno. [Read the official document to learn more](https://deno.land/[email protected]/getting_started/installation#download-and-install)
```sh
brew install deno
```
### 2. Run with Deno
```sh
deno run -A --unstable src/server-deno.ts
```

## Deploy to Deno Deploy

> You need to have Deno >= 1.32 installed.
```sh
deno run -A --unstable src/server-deno.ts
```
## Dev
Run `yarn start` to start development
```sh
Expand Down
3 changes: 3 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 30 additions & 33 deletions src/core/controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Handler, MiddlewareHandler } from "hono";
import { Bindings, DBConfig, Key, User } from "./type.ts";
import { Bindings, DBConfig, Key, User } from "../type.ts";
import { StatusCode } from "hono/utils/http-status";

export const users: Record<string, Handler<{ Bindings: Bindings }>> = {
async init(ctx) {
const { value: user } = await opencatDB.get<User>("user::id::0");
const { value: user } = await globalThis.opencatDB.get<User>("user::id::0");
if (user) {
return ctx.json(
{
Expand All @@ -24,7 +24,7 @@ export const users: Record<string, Handler<{ Bindings: Bindings }>> = {
key_id_count: 0,
};

const ok = await opencatDB.atomicOpt([
const ok = await globalThis.opencatDB.atomicOpt([
{ action: "check", args: [] },
{ action: "set", args: ["user::id::0", user] },
{ action: "set", args: ["db::config", dbConfig] },
Expand All @@ -38,14 +38,16 @@ export const users: Record<string, Handler<{ Bindings: Bindings }>> = {
}
},

async get_all(ctx) {
let users = await opencatDB.list<User>("user::id");
async getAll(ctx) {
let users = await globalThis.opencatDB.list<User>("user::id");
return ctx.json(users.map((user) => user.value));
},

async add(ctx) {
const { name } = await ctx.req.json();
const dbConfigEntry = await opencatDB.get<DBConfig>("db::config");
const dbConfigEntry = await globalThis.opencatDB.get<DBConfig>(
"db::config"
);

if (!dbConfigEntry.value) {
return ctx.json({ error: "db config not initialized" }, 403);
Expand All @@ -62,7 +64,7 @@ export const users: Record<string, Handler<{ Bindings: Bindings }>> = {
user_id_count: dbConfigEntry.value.user_id_count + 1,
};

const ok = await opencatDB.atomicOpt([
const ok = await globalThis.opencatDB.atomicOpt([
{ action: "check", args: [dbConfigEntry] },
{ action: "set", args: [`user::id::${user.id}`, user] },
{ action: "set", args: ["db::config", dbConfig] },
Expand All @@ -82,22 +84,24 @@ export const users: Record<string, Handler<{ Bindings: Bindings }>> = {
return ctx.json({ error: "id is not a number" }, 403);
}

await opencatDB.delete(`user::id::${Number(id)}`);
await globalThis.opencatDB.delete(`user::id::${Number(id)}`);

return ctx.json({ message: "ok" });
},

async reset(ctx) {
const id = ctx.req.param("id");
const userEntry = await opencatDB.get<User>(`user::id::${Number(id)}`);
const userEntry = await globalThis.opencatDB.get<User>(
`user::id::${Number(id)}`
);

if (!userEntry.value) {
return ctx.json({ error: "user not found" }, 404);
}

const user = { ...userEntry.value, token: tokenGen() };

const ok = await opencatDB.atomicOpt([
const ok = await globalThis.opencatDB.atomicOpt([
{ action: "check", args: [userEntry] },
{ action: "set", args: [`user::id::${Number(id)}`, user] },
]);
Expand All @@ -111,14 +115,14 @@ export const users: Record<string, Handler<{ Bindings: Bindings }>> = {
};

export const keys: Record<string, Handler<{ Bindings: Bindings }>> = {
async get_all(ctx) {
const keys = await opencatDB.list("key::id");
async getAll(ctx) {
const keys = await globalThis.opencatDB.list<Key>("key::id");
return ctx.json(keys.map((item) => item.value));
},

async add(ctx) {
const { name, key } = await ctx.req.json();
let dbConfigEntry = await opencatDB.get<DBConfig>("db::config");
let dbConfigEntry = await globalThis.opencatDB.get<DBConfig>("db::config");

if (!dbConfigEntry.value) {
return ctx.json({ error: "db metadata not initialized" }, 403);
Expand All @@ -130,16 +134,12 @@ export const keys: Record<string, Handler<{ Bindings: Bindings }>> = {
name,
};

await ctx.env.OPENCAT_DB.put(`key::id::${item.id}`, JSON.stringify(item), {
metadata: item,
});

const dbConfig = {
...dbConfigEntry.value,
key_id_count: dbConfigEntry.value.key_id_count + 1,
};

const ok = await opencatDB.atomicOpt([
const ok = await globalThis.opencatDB.atomicOpt([
{ action: "check", args: [dbConfigEntry] },
{ action: "set", args: [`key::id::${item.id}`, item] },
{ action: "set", args: ["db::config", dbConfig] },
Expand All @@ -158,15 +158,15 @@ export const keys: Record<string, Handler<{ Bindings: Bindings }>> = {
return ctx.json({ error: "id is not a number" }, 403);
}

await opencatDB.delete(`key::id::${Number(id)}`);
await globalThis.opencatDB.delete(`key::id::${Number(id)}`);

return ctx.json({ message: "ok" });
},
};

export const root: Record<string, Handler<{ Bindings: Bindings }>> = {
async whoami(ctx) {
let { value: user } = await opencatDB.get("user::id::0");
let { value: user } = await globalThis.opencatDB.get("user::id::0");
if (user) {
return ctx.json(user);
} else {
Expand All @@ -178,26 +178,23 @@ export const root: Record<string, Handler<{ Bindings: Bindings }>> = {
},
};

export const openai: Record<
string,
MiddlewareHandler<{ Bindings: Bindings }>
> = {
export const openai: Record<string, Handler<{ Bindings: Bindings }>> = {
async proxy(ctx) {
const keyEntries = await opencatDB.list<Key>("key::id");
const randomIndex = Math.floor(Math.random() * keys.keys.length);
const keyEntries = await globalThis.opencatDB.list<Key>("key::id");
const randomIndex = Math.floor(Math.random() * keyEntries.length);

const openaiToken = keyEntries[randomIndex].value?.key;

const req_headers = new Headers(ctx.req.headers);
const req_querys = new URLSearchParams(ctx.req.query()).toString();
const reqHeaders = new Headers(ctx.req.headers);
const reqQuerys = new URLSearchParams(ctx.req.query()).toString();

req_headers.set("Authorization", "Bearer " + openaiToken);
reqHeaders.set("Authorization", "Bearer " + openaiToken);

const request = new Request(
`${ctx.env.OPENAI_DOMAIN}${ctx.req.path}?${req_querys}`,
`${ctx.env.OPENAI_DOMAIN}${ctx.req.path}?${reqQuerys}`,
{
method: ctx.req.method,
headers: req_headers,
headers: reqHeaders,
body: ctx.req.body,
}
);
Expand Down Expand Up @@ -225,7 +222,7 @@ export const auth: Record<string, MiddlewareHandler<{ Bindings: Bindings }>> = {

const token = auth.slice(7);

const { value: user } = await opencatDB.get<User>("user::id::0");
const { value: user } = await globalThis.opencatDB.get<User>("user::id::0");

if (!user) {
return ctx.json({ error: "Unauthorized" }, 401);
Expand All @@ -248,7 +245,7 @@ export const auth: Record<string, MiddlewareHandler<{ Bindings: Bindings }>> = {

const token = auth.slice(7);

const users = await opencatDB.list<User>("user::id");
const users = await globalThis.opencatDB.list<User>("user::id");

const existed = users.find((user) => user.value?.token === token);
if (existed) {
Expand Down
10 changes: 5 additions & 5 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Hono } from "hono";
import controller from "./controller.ts";
import { Bindings } from "./type.ts";
import { Bindings } from "../type.ts";

const app = new Hono<{ Bindings: Bindings }>();
const middleware = {
Expand All @@ -12,18 +12,18 @@ const keys = new Hono<{ Bindings: Bindings }>();
const root = new Hono<{ Bindings: Bindings }>();
const openai = new Hono<{ Bindings: Bindings }>();

users.get("/", controller.users.get_all);
users.get("/", controller.users.getAll);
users.post("/", controller.users.add);
users.delete("/", controller.users.delete);
users.delete("/:id", controller.users.delete);
users.post("/:id/reset", controller.users.reset);

keys.get("/", controller.keys.get_all);
keys.get("/", controller.keys.getAll);
keys.post("/", controller.keys.add);
keys.delete("/:id", controller.keys.delete);

root.get("/", controller.root.whoami);

openai.use("/*", controller.openai.proxy);
openai.all("/*", controller.openai.proxy);

middleware.root.use("*", controller.auth.root);
middleware.openai.use("*", controller.auth.openai);
Expand Down
4 changes: 2 additions & 2 deletions src/deno/db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AtomicOpt, KV, PrefixK, RemoveUnion } from "../core/type.ts";
import { AtomicOpt, KV, PrefixK, RemoveUnion } from "../type.ts";

export class KVDeno<Key extends string = string> implements KV<Key> {
export class DenoKV<Key extends string = string> implements KV<Key> {
constructor(private db: Deno.Kv) {}

private atomic() {
Expand Down
4 changes: 2 additions & 2 deletions src/server-deno.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { serve } from "deno/server";
import app from "./core/index.ts";
import { KVDeno } from "./deno/db.ts";
import { DenoKV } from "./deno/db.ts";
import { load } from "dotenv";

await load();

globalThis.tokenGen = crypto.randomUUID;
globalThis.opencatDB = new KVDeno(await Deno.openKv());
globalThis.opencatDB = new DenoKV(await Deno.openKv());

serve(app.fetch);
24 changes: 20 additions & 4 deletions src/server-worker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import app from "./core/index";
import { uuid } from "@cfworker/uuid";
import { KVWorker } from "./worker/db";
import { WorkerKV } from "./worker/db";
import { Hono } from "hono";
import { Bindings } from "./type";

globalThis.tokenGen = uuid;
globalThis.opencatDB = new KVWorker(OPENCAT_DB);
export default app;
const server = new Hono<{ Bindings: Bindings }>();

server.use((ctx, next) => {
if (!globalThis.tokenGen) {
globalThis.tokenGen = uuid;
}

if (!globalThis.opencatDB) {
globalThis.opencatDB = new WorkerKV(ctx.env.OPENCAT_DB);
}

return next();
});

server.route("/", app);

export default server;
2 changes: 1 addition & 1 deletion src/core/type.ts → src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export type AtomicOperation = "check" | "delete" | "set";
declare global {
function getMiniflareBindings(): Bindings;
function tokenGen(): string;
const OPENCAT_DB: KVNamespace<UserK | KeyK | DBConfigK>;
var opencatDB: KV<UserK | KeyK | DBConfigK>;
const OPENCAT_DB: KVNamespace<UserK | KeyK | DBConfigK>;
}

export type RemoveUnion<T, U> = T extends U ? never : T;
11 changes: 5 additions & 6 deletions src/worker/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
KV,
PrefixK,
RemoveUnion,
} from "../core/type.ts";
} from "../type.ts";

export class KVWorker<Key extends string = string> implements KV<Key> {
export class WorkerKV<Key extends string = string> implements KV<Key> {
constructor(private db: KVNamespace<Key>) {}

async get<Value>(key: Key) {
Expand Down Expand Up @@ -70,10 +70,9 @@ export class KVWorker<Key extends string = string> implements KV<Key> {
//@ts-ignore
action = "put";
}
return this.db[
action as RemoveUnion<AtomicOperation, "check" | "set">
//@ts-ignore
](...args);

//@ts-ignore
return this[action](...args);
})
);

Expand Down
3 changes: 1 addition & 2 deletions wrangler.bak.toml → wrangler.toml.bak
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
name = "opencatd_worker"
main = "src/index.ts"
main = "src/server-worker.ts"
compatibility_date = "2023-03-30"
routes = [{ pattern = "xxxxxxxx", custom_domain = true }]
kv_namespaces = [{ binding = "OPENCAT_DB", id = "xxxxxxxxxxx" }]
vars = { OPENAI_DOMAIN = "https://api.openai.com" }

0 comments on commit fbf6a73

Please sign in to comment.