+2
-1
.gitignore
+2
-1
.gitignore
+17
apps/aqua/.drizzle/0000_same_maelstrom.sql
+17
apps/aqua/.drizzle/0000_same_maelstrom.sql
···
1
+
CREATE TABLE `auth_session` (
2
+
`key` text PRIMARY KEY NOT NULL,
3
+
`session` text NOT NULL
4
+
);
5
+
--> statement-breakpoint
6
+
CREATE TABLE `auth_state` (
7
+
`key` text PRIMARY KEY NOT NULL,
8
+
`state` text NOT NULL
9
+
);
10
+
--> statement-breakpoint
11
+
CREATE TABLE `status` (
12
+
`uri` text PRIMARY KEY NOT NULL,
13
+
`authorDid` text NOT NULL,
14
+
`status` text NOT NULL,
15
+
`createdAt` text NOT NULL,
16
+
`indexedAt` text NOT NULL
17
+
);
+111
apps/aqua/.drizzle/meta/0000_snapshot.json
+111
apps/aqua/.drizzle/meta/0000_snapshot.json
···
1
+
{
2
+
"version": "6",
3
+
"dialect": "sqlite",
4
+
"id": "417cce08-b23b-4a9f-ad7e-e96215e0fb38",
5
+
"prevId": "00000000-0000-0000-0000-000000000000",
6
+
"tables": {
7
+
"auth_session": {
8
+
"name": "auth_session",
9
+
"columns": {
10
+
"key": {
11
+
"name": "key",
12
+
"type": "text",
13
+
"primaryKey": true,
14
+
"notNull": true,
15
+
"autoincrement": false
16
+
},
17
+
"session": {
18
+
"name": "session",
19
+
"type": "text",
20
+
"primaryKey": false,
21
+
"notNull": true,
22
+
"autoincrement": false
23
+
}
24
+
},
25
+
"indexes": {},
26
+
"foreignKeys": {},
27
+
"compositePrimaryKeys": {},
28
+
"uniqueConstraints": {},
29
+
"checkConstraints": {}
30
+
},
31
+
"auth_state": {
32
+
"name": "auth_state",
33
+
"columns": {
34
+
"key": {
35
+
"name": "key",
36
+
"type": "text",
37
+
"primaryKey": true,
38
+
"notNull": true,
39
+
"autoincrement": false
40
+
},
41
+
"state": {
42
+
"name": "state",
43
+
"type": "text",
44
+
"primaryKey": false,
45
+
"notNull": true,
46
+
"autoincrement": false
47
+
}
48
+
},
49
+
"indexes": {},
50
+
"foreignKeys": {},
51
+
"compositePrimaryKeys": {},
52
+
"uniqueConstraints": {},
53
+
"checkConstraints": {}
54
+
},
55
+
"status": {
56
+
"name": "status",
57
+
"columns": {
58
+
"uri": {
59
+
"name": "uri",
60
+
"type": "text",
61
+
"primaryKey": true,
62
+
"notNull": true,
63
+
"autoincrement": false
64
+
},
65
+
"authorDid": {
66
+
"name": "authorDid",
67
+
"type": "text",
68
+
"primaryKey": false,
69
+
"notNull": true,
70
+
"autoincrement": false
71
+
},
72
+
"status": {
73
+
"name": "status",
74
+
"type": "text",
75
+
"primaryKey": false,
76
+
"notNull": true,
77
+
"autoincrement": false
78
+
},
79
+
"createdAt": {
80
+
"name": "createdAt",
81
+
"type": "text",
82
+
"primaryKey": false,
83
+
"notNull": true,
84
+
"autoincrement": false
85
+
},
86
+
"indexedAt": {
87
+
"name": "indexedAt",
88
+
"type": "text",
89
+
"primaryKey": false,
90
+
"notNull": true,
91
+
"autoincrement": false
92
+
}
93
+
},
94
+
"indexes": {},
95
+
"foreignKeys": {},
96
+
"compositePrimaryKeys": {},
97
+
"uniqueConstraints": {},
98
+
"checkConstraints": {}
99
+
}
100
+
},
101
+
"views": {},
102
+
"enums": {},
103
+
"_meta": {
104
+
"schemas": {},
105
+
"tables": {},
106
+
"columns": {}
107
+
},
108
+
"internal": {
109
+
"indexes": {}
110
+
}
111
+
}
+13
apps/aqua/.drizzle/meta/_journal.json
+13
apps/aqua/.drizzle/meta/_journal.json
+11
apps/aqua/.env
+11
apps/aqua/.env
···
1
+
PRIVATE_KEY_1={"kty":"EC","d":"8rx-D2vaik7FgRUaMeK_M8yZQ57J5NFs4MP6300_gek","use":"sig","crv":"P-256","kid":"44J2ZYr_4O3wp1B8GSRPvHMb7Cf506Nss3ISOplRx9I","x":"f9RLs9sqKyL38dPKsQaX-P_qTHVnNRCXuzkjbPvh7Ls","y":"ZnH5GuTAl5TTb-hZzsVgf1kUl4OB6qCS0PmM4_SPXvw","alg":"ES256"}
2
+
PRIVATE_KEY_2={"kty":"EC","d":"5Rk8UuCz-chUX_OZ4WgB7lb3OELn-xlGEedk4P-qY_M","use":"sig","crv":"P-256","kid":"kzDrzoJdNtK3bfTiuJYQZSk_Z7nsZqEpzqYSqHVBN_Q","x":"WuMNQ3slMhmvUJze-q4pxmC_Xqu5MkpkD3eSh1dPDBs","y":"0CS96lObk2UWnRbbrhQQDbduyZ_A4zKZtwSQTfqVkcU","alg":"ES256"}
3
+
PRIVATE_KEY_3={"kty":"EC","d":"GvpzAoGaHCG3OFe8qqi8FRs3WShGvS8OAOhjcN2vyuQ","use":"sig","crv":"P-256","kid":"y0HFLgCqOSwfbRJdO48dM8prLrLrT-qxNs_UrdvrbNQ","x":"VJ13t663tWZa67wUNQw26iU9iatIg4ZIklNKOrqMiYw","y":"Fqyc7qiOfwaYDXO259G8T66Wg2Kf_WLEjyi0ZenX2pI","alg":"ES256"}
4
+
NODE_ENV=development # Options: development, production
5
+
PORT=3000 # The port your server will listen on
6
+
HOST=localhost # Hostname for the server
7
+
PUBLIC_URL= # Set when deployed publicly, e.g. "https://mysite.com". Informs OAuth client id.
8
+
DB_PATH=:memory: # The SQLite database path. Leave as ":memory:" to use a temporary in-memory database.
9
+
# Secrets
10
+
# Must set this in production. May be generated with `openssl rand -base64 33`=undefined
11
+
# COOKIE_SECRET=""
apps/aqua/bun.lockb
apps/aqua/bun.lockb
This is a binary file and will not be displayed.
apps/aqua/db.sqlite
apps/aqua/db.sqlite
This is a binary file and will not be displayed.
+55
apps/aqua/db/schema.ts
+55
apps/aqua/db/schema.ts
···
1
+
import { defineConfig } from "drizzle-kit";
2
+
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
export default defineConfig({
5
+
dialect: "sqlite", // 'mysql' | 'postgresql' | 'sqlite' | 'turso'
6
+
schema: "./src/db/schema.ts",
7
+
});
8
+
9
+
export type DatabaseSchema = {
10
+
status: Status;
11
+
auth_session: AuthSession;
12
+
auth_state: AuthState;
13
+
};
14
+
15
+
export type Status = {
16
+
uri: string;
17
+
authorDid: string;
18
+
status: string;
19
+
createdAt: string;
20
+
indexedAt: string;
21
+
};
22
+
23
+
export type AuthSession = {
24
+
key: string;
25
+
session: AuthSessionJson;
26
+
};
27
+
28
+
export type AuthState = {
29
+
key: string;
30
+
state: AuthStateJson;
31
+
};
32
+
33
+
type AuthStateJson = string;
34
+
35
+
type AuthSessionJson = string;
36
+
37
+
// Tables
38
+
39
+
export const status = sqliteTable("status", {
40
+
uri: text().primaryKey(),
41
+
authorDid: text().notNull(),
42
+
status: text().notNull(),
43
+
createdAt: text().notNull(),
44
+
indexedAt: text().notNull(),
45
+
});
46
+
47
+
export const authSession = sqliteTable("auth_session", {
48
+
key: text().primaryKey(),
49
+
session: text().notNull(),
50
+
});
51
+
52
+
export const authState = sqliteTable("auth_state", {
53
+
key: text().primaryKey(),
54
+
state: text().notNull(),
55
+
});
+12
apps/aqua/drizzle.config.ts
+12
apps/aqua/drizzle.config.ts
···
1
+
import { defineConfig } from "drizzle-kit";
2
+
import process from "node:process";
3
+
4
+
export default defineConfig({
5
+
dialect: "sqlite",
6
+
schema: "./db/schema.ts",
7
+
out: "./.drizzle",
8
+
casing: "snake_case",
9
+
dbCredentials: {
10
+
url: process.env.DATABASE_URL ?? "./db.sqlite",
11
+
},
12
+
});
+60
apps/aqua/package.json
+60
apps/aqua/package.json
···
1
+
{
2
+
"name": "@teal/aqua",
3
+
"type": "module",
4
+
"version": "1.0.50",
5
+
"main": "index.ts",
6
+
"packageManager": "bun@1.0.0+",
7
+
"scripts": {
8
+
"dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty",
9
+
"bun:dev": "bun run src/index.ts | pino-pretty",
10
+
"build": "tsup",
11
+
"start": "node dist/index.js",
12
+
"lexgen": "lex gen-server ./src/lexicon ./lexicons/*",
13
+
"clean": "rimraf dist coverage",
14
+
"check-types": "tsc --noEmit",
15
+
"db:migrate": "drizzle-kit migrate",
16
+
"db:seed": "drizzle-kit seed",
17
+
"db:studio": "drizzle-kit studio"
18
+
},
19
+
"dependencies": {
20
+
"@atproto/api": "^0.13.14",
21
+
"@atproto/common": "^0.4.4",
22
+
"@atproto/identity": "^0.4.2",
23
+
"@atproto/lexicon": "^0.4.2",
24
+
"@atproto/oauth-client-node": "^0.1.4",
25
+
"@atproto/sync": "^0.1.4",
26
+
"@atproto/syntax": "^0.3.0",
27
+
"@atproto/xrpc-server": "^0.6.4",
28
+
"@hono/node-server": "^1.13.4",
29
+
"@libsql/client": "^0.14.0",
30
+
"dotenv": "^16.4.5",
31
+
"drizzle-orm": "^0.35.3",
32
+
"envalid": "^8.0.0",
33
+
"hono": "^4.6.8",
34
+
"jose": "^5.9.6",
35
+
"pino": "^9.5.0",
36
+
"turbo": "^2.2.3",
37
+
"uhtml": "^4.5.11"
38
+
},
39
+
"devDependencies": {
40
+
"@teal/tsconfig": "*",
41
+
"@atproto/lex-cli": "^0.4.1",
42
+
"@types/node": "^20.17.5",
43
+
"drizzle-kit": "^0.26.2",
44
+
"pino-pretty": "^11.3.0",
45
+
"rimraf": "^6.0.1",
46
+
"tsup": "^8.3.5",
47
+
"tsx": "^4.19.2",
48
+
"typescript": "^5.6.3"
49
+
},
50
+
"tsup": {
51
+
"entry": [
52
+
"src",
53
+
"!src/**/__tests__/**",
54
+
"!src/**/*.test.*"
55
+
],
56
+
"splitting": false,
57
+
"sourcemap": true,
58
+
"clean": true
59
+
}
60
+
}
+31
apps/aqua/scripts/generateJWKS.ts
+31
apps/aqua/scripts/generateJWKS.ts
···
1
+
import { generateKeyPair, exportJWK } from "jose";
2
+
import { randomBytes } from "crypto";
3
+
4
+
// Function to create JWKS and append to .env file
5
+
async function createJWKS() {
6
+
const jwks: any = { keys: [] };
7
+
8
+
for (let i = 0; i < 3; i++) {
9
+
// Generate a new key pair
10
+
// const alg = "ES256";
11
+
// const { publicKey, privateKey } = await generateKeyPair(alg, {
12
+
// extractable: true,
13
+
// });
14
+
// // Export the key pair as a JWK
15
+
// const jwk = await exportJWK(privateKey);
16
+
// jwk.kid = randomBytes(16).toString("hex");
17
+
// jwk.alg = alg;
18
+
// jwks.keys.push(jwk);
19
+
20
+
// TODO: replace this ASAP if in prod
21
+
let res = await fetch(
22
+
"https://mkjwk.org/jwk/ec?alg=ES256&use=sig&gen=sha256&crv=P-256",
23
+
);
24
+
let jwk_res = await res.json();
25
+
let jwk = jwk_res.jwk;
26
+
console.log("PRIVATE_KEY_" + (i + 1) + "=" + JSON.stringify(jwk));
27
+
}
28
+
}
29
+
30
+
// Execute the function
31
+
createJWKS().catch(console.error);
+28
apps/aqua/src/auth/client.ts
+28
apps/aqua/src/auth/client.ts
···
1
+
import { NodeOAuthClient } from "@atproto/oauth-client-node";
2
+
import type { Database } from "@/db";
3
+
import { env } from "@/lib/env";
4
+
import { SessionStore, StateStore } from "./storage";
5
+
6
+
export const createClient = async (db: Database) => {
7
+
const publicUrl = env.PUBLIC_URL;
8
+
const url = publicUrl || `http://127.0.0.1:${env.PORT}`;
9
+
const enc = encodeURIComponent;
10
+
return new NodeOAuthClient({
11
+
clientMetadata: {
12
+
client_name: "Teal",
13
+
client_id: publicUrl
14
+
? `${url}/client-metadata.json`
15
+
: `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`,
16
+
client_uri: url,
17
+
redirect_uris: [`${url}/oauth/callback`],
18
+
scope: "atproto transition:generic",
19
+
grant_types: ["authorization_code", "refresh_token"],
20
+
response_types: ["code"],
21
+
application_type: "web",
22
+
token_endpoint_auth_method: "none",
23
+
dpop_bound_access_tokens: true,
24
+
},
25
+
stateStore: new StateStore(db),
26
+
sessionStore: new SessionStore(db),
27
+
});
28
+
};
+82
apps/aqua/src/auth/router.ts
+82
apps/aqua/src/auth/router.ts
···
1
+
import { createClient } from "./client";
2
+
import { db } from "@/db";
3
+
import { EnvWithCtx, TealContext } from "@/ctx";
4
+
import { authSession } from "../../db/schema";
5
+
import { Hono } from "hono";
6
+
7
+
interface LoginBody {
8
+
handle?: string;
9
+
}
10
+
11
+
export const login = async (c: TealContext) => {
12
+
let body: LoginBody = await c.req.json();
13
+
// Initiate the OAuth flow
14
+
const auth = await createClient(db);
15
+
if (!body) return Response.json({ error: "Could not parse body" });
16
+
// handle is the handle of the user
17
+
if (!body.handle && body.handle === undefined)
18
+
return Response.json({ error: "Handle is required" });
19
+
try {
20
+
const url = await auth.authorize(body.handle, {
21
+
scope: "atproto transition:generic",
22
+
state: crypto.randomUUID(),
23
+
});
24
+
return Response.json({ redirect_to: url });
25
+
} catch (e) {
26
+
console.error(e);
27
+
return Response.json({ error: "Could not authorize user" });
28
+
}
29
+
};
30
+
31
+
export async function loginGet(handle: string) {
32
+
// Initiate the OAuth flow
33
+
const auth = await createClient(db);
34
+
try {
35
+
const url = await auth.authorize("natalie.sh", {
36
+
scope: "atproto transition:generic",
37
+
});
38
+
return Response.redirect(url);
39
+
} catch (e) {
40
+
console.error(e);
41
+
return Response.json({ error: "Could not authorize user" });
42
+
}
43
+
}
44
+
45
+
export async function callback(c: TealContext) {
46
+
// Initiate the OAuth flow
47
+
const auth = await createClient(db);
48
+
try {
49
+
const honoParams = c.req.query();
50
+
console.log("params", honoParams);
51
+
const params = new URLSearchParams(honoParams);
52
+
const cb = await auth.callback(params);
53
+
54
+
// generate a session key (random)
55
+
56
+
const sessionKey = crypto.randomUUID();
57
+
58
+
// insert in session table, return data from cb
59
+
60
+
c.var.db.insert(authSession).values({
61
+
key: sessionKey,
62
+
session: JSON.stringify(cb.session),
63
+
});
64
+
65
+
return Response.json(cb);
66
+
} catch (e) {
67
+
console.error(e);
68
+
return Response.json({ error: "Could not authorize user" });
69
+
}
70
+
}
71
+
72
+
const app = new Hono<EnvWithCtx>();
73
+
74
+
app.get("/login", async (c) => loginGet("natalie.sh"));
75
+
76
+
app.post("/login", async (c) => login(c));
77
+
78
+
app.get("/callback", async (c) => callback(c));
79
+
80
+
export const getAuthRouter = () => {
81
+
return app;
82
+
};
+85
apps/aqua/src/auth/storage.ts
+85
apps/aqua/src/auth/storage.ts
···
1
+
import type {
2
+
NodeSavedSession,
3
+
NodeSavedSessionStore,
4
+
NodeSavedState,
5
+
NodeSavedStateStore,
6
+
} from "@atproto/oauth-client-node";
7
+
import type { Database } from "@/db";
8
+
import { authSession, authState } from "../../db/schema";
9
+
import { eq } from "drizzle-orm";
10
+
11
+
export class StateStore implements NodeSavedStateStore {
12
+
constructor(private db: Database) {}
13
+
async get(key: string): Promise<NodeSavedState | undefined> {
14
+
const result = await this.db
15
+
.select()
16
+
.from(authState)
17
+
.where(eq(authState.key, key))
18
+
.limit(1)
19
+
.execute();
20
+
// .selectFrom("auth_state")
21
+
// .selectAll()
22
+
// .where("key", "=", key)
23
+
// .executeTakeFirst();
24
+
console.log("getting state", key, result);
25
+
if (!result[0]) return;
26
+
return JSON.parse(result[0].state) as NodeSavedState;
27
+
}
28
+
async set(key: string, val: NodeSavedState) {
29
+
const state = JSON.stringify(val);
30
+
console.log("inserting state", key, state);
31
+
await this.db
32
+
.insert(authState)
33
+
.values({ key, state })
34
+
.onConflictDoUpdate({
35
+
set: { state: state },
36
+
target: authState.key,
37
+
})
38
+
.execute();
39
+
// .insertInto("auth_state")
40
+
// .values({ key, state })
41
+
// .onConflict((oc) => oc.doUpdateSet({ state }))
42
+
// .execute();
43
+
}
44
+
async del(key: string) {
45
+
await this.db.delete(authState).where(eq(authState.key, key)).execute();
46
+
//.deleteFrom("auth_state").where("key", "=", key).execute();
47
+
}
48
+
}
49
+
50
+
export class SessionStore implements NodeSavedSessionStore {
51
+
constructor(private db: Database) {}
52
+
async get(key: string): Promise<NodeSavedSession | undefined> {
53
+
const result = await this.db
54
+
.select()
55
+
.from(authSession)
56
+
.where(eq(authSession.key, key))
57
+
.limit(1)
58
+
.all();
59
+
// .selectFrom("auth_session")
60
+
// .selectAll()
61
+
// .where("key", "=", key)
62
+
// .executeTakeFirst();
63
+
if (!result[0]) return;
64
+
return JSON.parse(result[0].session) as NodeSavedSession;
65
+
}
66
+
async set(key: string, val: NodeSavedSession) {
67
+
const session = JSON.stringify(val);
68
+
await this.db
69
+
.insert(authSession)
70
+
.values({ key, session })
71
+
.onConflictDoUpdate({
72
+
set: { session: session },
73
+
target: authSession.key,
74
+
})
75
+
.execute();
76
+
// .insertInto("auth_session")
77
+
// .values({ key, session })
78
+
// .onConflict((oc) => oc.doUpdateSet({ session }))
79
+
// .execute();
80
+
}
81
+
async del(key: string) {
82
+
await this.db.delete(authSession).where(eq(authSession.key, key)).execute();
83
+
//.deleteFrom("auth_session").where("key", "=", key).execute();
84
+
}
85
+
}
+36
apps/aqua/src/ctx.ts
+36
apps/aqua/src/ctx.ts
···
1
+
import { NodeOAuthClient } from "@atproto/oauth-client-node";
2
+
import { Client } from "@libsql/client/.";
3
+
import { LibSQLDatabase } from "drizzle-orm/libsql";
4
+
import { Context, Next } from "hono";
5
+
import { createClient } from "./auth/client";
6
+
import { Logger } from "pino";
7
+
8
+
export type TealContext = Context<EnvWithCtx, any, any>;
9
+
10
+
export type EnvWithCtx = {
11
+
Variables: Ctx;
12
+
};
13
+
14
+
export type Ctx = {
15
+
auth: NodeOAuthClient;
16
+
db: LibSQLDatabase<typeof import("/Users/natalie/Code/teal/db/schema")> & {
17
+
$client: Client;
18
+
};
19
+
logger: Logger<never, boolean>;
20
+
};
21
+
22
+
export const setupContext = async (
23
+
c: TealContext,
24
+
db: LibSQLDatabase<typeof import("/Users/natalie/Code/teal/db/schema")> & {
25
+
$client: Client;
26
+
},
27
+
logger: Logger<never, boolean>,
28
+
next: Next,
29
+
) => {
30
+
const auth = await createClient(db);
31
+
32
+
c.set("db", db);
33
+
c.set("auth", auth);
34
+
c.set("logger", logger);
35
+
await next();
36
+
};
+12
apps/aqua/src/db.ts
+12
apps/aqua/src/db.ts
···
1
+
import { drizzle } from "drizzle-orm/libsql";
2
+
import * as schema from "../db/schema";
3
+
import process from "node:process";
4
+
5
+
export const db = drizzle({
6
+
connection: process.env.DATABASE_URL ?? "file:./db.sqlite",
7
+
// doesn't seem to work?
8
+
//casing: "snake_case",
9
+
schema: schema,
10
+
});
11
+
12
+
export type Database = typeof db;
+42
apps/aqua/src/index.ts
+42
apps/aqua/src/index.ts
···
1
+
import { serve as serveNode } from "@hono/node-server";
2
+
import { Hono } from "hono";
3
+
import { db } from "@/db";
4
+
import { getAuthRouter, loginGet } from "./auth/router";
5
+
import pino from "pino";
6
+
import { EnvWithCtx, setupContext } from "./ctx";
7
+
import { env } from "./lib/env";
8
+
9
+
const logger = pino({ name: "server start" });
10
+
11
+
const app = new Hono<EnvWithCtx>();
12
+
13
+
app.use((c, next) => setupContext(c, db, logger, next));
14
+
15
+
app.get("/", (c) => c.text("Hono meets Node.js"));
16
+
17
+
app.get("/info", async (c) => {
18
+
const result = await db.query.status.findFirst().execute();
19
+
console.log("result", result);
20
+
return c.json(result);
21
+
});
22
+
23
+
app.route("/oauth", getAuthRouter());
24
+
25
+
// const run = async () => {
26
+
// serve(
27
+
// {
28
+
// fetch: app.fetch,
29
+
// port: env.PORT,
30
+
// hostname: env.HOST,
31
+
// },
32
+
// (info) => {
33
+
// console.log(
34
+
// `Listening on ${info.address == "::1" ? "http://localhost" : info.address}:${info.port} (${info.family})`,
35
+
// );
36
+
// },
37
+
// );
38
+
// };
39
+
40
+
// run();
41
+
42
+
export default app;
+99
apps/aqua/src/lexicon/index.ts
+99
apps/aqua/src/lexicon/index.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import {
5
+
createServer as createXrpcServer,
6
+
Server as XrpcServer,
7
+
Options as XrpcOptions,
8
+
AuthVerifier,
9
+
StreamAuthVerifier,
10
+
} from '@atproto/xrpc-server'
11
+
import { schemas } from './lexicons'
12
+
13
+
export function createServer(options?: XrpcOptions): Server {
14
+
return new Server(options)
15
+
}
16
+
17
+
export class Server {
18
+
xrpc: XrpcServer
19
+
app: AppNS
20
+
xyz: XyzNS
21
+
22
+
constructor(options?: XrpcOptions) {
23
+
this.xrpc = createXrpcServer(schemas, options)
24
+
this.app = new AppNS(this)
25
+
this.xyz = new XyzNS(this)
26
+
}
27
+
}
28
+
29
+
export class AppNS {
30
+
_server: Server
31
+
bsky: AppBskyNS
32
+
33
+
constructor(server: Server) {
34
+
this._server = server
35
+
this.bsky = new AppBskyNS(server)
36
+
}
37
+
}
38
+
39
+
export class AppBskyNS {
40
+
_server: Server
41
+
actor: AppBskyActorNS
42
+
43
+
constructor(server: Server) {
44
+
this._server = server
45
+
this.actor = new AppBskyActorNS(server)
46
+
}
47
+
}
48
+
49
+
export class AppBskyActorNS {
50
+
_server: Server
51
+
52
+
constructor(server: Server) {
53
+
this._server = server
54
+
}
55
+
}
56
+
57
+
export class XyzNS {
58
+
_server: Server
59
+
statusphere: XyzStatusphereNS
60
+
61
+
constructor(server: Server) {
62
+
this._server = server
63
+
this.statusphere = new XyzStatusphereNS(server)
64
+
}
65
+
}
66
+
67
+
export class XyzStatusphereNS {
68
+
_server: Server
69
+
70
+
constructor(server: Server) {
71
+
this._server = server
72
+
}
73
+
}
74
+
75
+
type SharedRateLimitOpts<T> = {
76
+
name: string
77
+
calcKey?: (ctx: T) => string
78
+
calcPoints?: (ctx: T) => number
79
+
}
80
+
type RouteRateLimitOpts<T> = {
81
+
durationMs: number
82
+
points: number
83
+
calcKey?: (ctx: T) => string
84
+
calcPoints?: (ctx: T) => number
85
+
}
86
+
type HandlerOpts = { blobLimit?: number }
87
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
88
+
type ConfigOf<Auth, Handler, ReqCtx> =
89
+
| Handler
90
+
| {
91
+
auth?: Auth
92
+
opts?: HandlerOpts
93
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
94
+
handler: Handler
95
+
}
96
+
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
97
+
Awaited<ReturnType<AV>>,
98
+
{ credentials: unknown }
99
+
>
+94
apps/aqua/src/lexicon/lexicons.ts
+94
apps/aqua/src/lexicon/lexicons.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
5
+
6
+
export const schemaDict = {
7
+
AppBskyActorProfile: {
8
+
lexicon: 1,
9
+
id: 'app.bsky.actor.profile',
10
+
defs: {
11
+
main: {
12
+
type: 'record',
13
+
description: 'A declaration of a Bluesky account profile.',
14
+
key: 'literal:self',
15
+
record: {
16
+
type: 'object',
17
+
properties: {
18
+
displayName: {
19
+
type: 'string',
20
+
maxGraphemes: 64,
21
+
maxLength: 640,
22
+
},
23
+
description: {
24
+
type: 'string',
25
+
description: 'Free-form profile description text.',
26
+
maxGraphemes: 256,
27
+
maxLength: 2560,
28
+
},
29
+
avatar: {
30
+
type: 'blob',
31
+
description:
32
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
33
+
accept: ['image/png', 'image/jpeg'],
34
+
maxSize: 1000000,
35
+
},
36
+
banner: {
37
+
type: 'blob',
38
+
description:
39
+
'Larger horizontal image to display behind profile view.',
40
+
accept: ['image/png', 'image/jpeg'],
41
+
maxSize: 1000000,
42
+
},
43
+
labels: {
44
+
type: 'union',
45
+
description:
46
+
'Self-label values, specific to the Bluesky application, on the overall account.',
47
+
refs: ['lex:com.atproto.label.defs#selfLabels'],
48
+
},
49
+
joinedViaStarterPack: {
50
+
type: 'ref',
51
+
ref: 'lex:com.atproto.repo.strongRef',
52
+
},
53
+
createdAt: {
54
+
type: 'string',
55
+
format: 'datetime',
56
+
},
57
+
},
58
+
},
59
+
},
60
+
},
61
+
},
62
+
XyzStatusphereStatus: {
63
+
lexicon: 1,
64
+
id: 'xyz.statusphere.status',
65
+
defs: {
66
+
main: {
67
+
type: 'record',
68
+
key: 'tid',
69
+
record: {
70
+
type: 'object',
71
+
required: ['status', 'createdAt'],
72
+
properties: {
73
+
status: {
74
+
type: 'string',
75
+
minLength: 1,
76
+
maxGraphemes: 1,
77
+
maxLength: 32,
78
+
},
79
+
createdAt: {
80
+
type: 'string',
81
+
format: 'datetime',
82
+
},
83
+
},
84
+
},
85
+
},
86
+
},
87
+
},
88
+
}
89
+
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
90
+
export const lexicons: Lexicons = new Lexicons(schemas)
91
+
export const ids = {
92
+
AppBskyActorProfile: 'app.bsky.actor.profile',
93
+
XyzStatusphereStatus: 'xyz.statusphere.status',
94
+
}
+38
apps/aqua/src/lexicon/types/app/bsky/actor/profile.ts
+38
apps/aqua/src/lexicon/types/app/bsky/actor/profile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
9
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
10
+
11
+
export interface Record {
12
+
displayName?: string
13
+
/** Free-form profile description text. */
14
+
description?: string
15
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
+
avatar?: BlobRef
17
+
/** Larger horizontal image to display behind profile view. */
18
+
banner?: BlobRef
19
+
labels?:
20
+
| ComAtprotoLabelDefs.SelfLabels
21
+
| { $type: string; [k: string]: unknown }
22
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
23
+
createdAt?: string
24
+
[k: string]: unknown
25
+
}
26
+
27
+
export function isRecord(v: unknown): v is Record {
28
+
return (
29
+
isObj(v) &&
30
+
hasProp(v, '$type') &&
31
+
(v.$type === 'app.bsky.actor.profile#main' ||
32
+
v.$type === 'app.bsky.actor.profile')
33
+
)
34
+
}
35
+
36
+
export function validateRecord(v: unknown): ValidationResult {
37
+
return lexicons.validate('app.bsky.actor.profile#main', v)
38
+
}
+26
apps/aqua/src/lexicon/types/xyz/statusphere/status.ts
+26
apps/aqua/src/lexicon/types/xyz/statusphere/status.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Record {
10
+
status: string
11
+
createdAt: string
12
+
[k: string]: unknown
13
+
}
14
+
15
+
export function isRecord(v: unknown): v is Record {
16
+
return (
17
+
isObj(v) &&
18
+
hasProp(v, '$type') &&
19
+
(v.$type === 'xyz.statusphere.status#main' ||
20
+
v.$type === 'xyz.statusphere.status')
21
+
)
22
+
}
23
+
24
+
export function validateRecord(v: unknown): ValidationResult {
25
+
return lexicons.validate('xyz.statusphere.status#main', v)
26
+
}
+13
apps/aqua/src/lexicon/util.ts
+13
apps/aqua/src/lexicon/util.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
export function isObj(v: unknown): v is Record<string, unknown> {
5
+
return typeof v === 'object' && v !== null
6
+
}
7
+
8
+
export function hasProp<K extends PropertyKey>(
9
+
data: object,
10
+
prop: K,
11
+
): data is Record<K, unknown> {
12
+
return prop in data
13
+
}
+17
apps/aqua/src/lib/env.ts
+17
apps/aqua/src/lib/env.ts
···
1
+
import dotenv from "dotenv";
2
+
import { cleanEnv, host, port, str, testOnly } from "envalid";
3
+
import process from "node:process";
4
+
5
+
dotenv.config();
6
+
7
+
export const env = cleanEnv(process.env, {
8
+
NODE_ENV: str({
9
+
devDefault: testOnly("test"),
10
+
choices: ["development", "production", "test"],
11
+
}),
12
+
HOST: host({ devDefault: testOnly("0.0.0.0") }),
13
+
PORT: port({ devDefault: testOnly(3000) }),
14
+
PUBLIC_URL: str({}),
15
+
DB_PATH: str({ devDefault: "file:./db.sqlite" }),
16
+
COOKIE_SECRET: str({ devDefault: "secret_cookie! very secret!" }),
17
+
});
+12
apps/aqua/src/lib/view.ts
+12
apps/aqua/src/lib/view.ts
···
1
+
// @ts-ignore
2
+
import ssr from "uhtml/ssr";
3
+
import type initSSR from "uhtml/types/init-ssr";
4
+
import type { Hole } from "uhtml/types/keyed";
5
+
6
+
export type { Hole };
7
+
8
+
export const { html }: ReturnType<typeof initSSR> = ssr();
9
+
10
+
export function page(hole: Hole) {
11
+
return `<!DOCTYPE html>\n${hole.toDOM().toString()}`;
12
+
}
+8
apps/aqua/tsconfig.json
+8
apps/aqua/tsconfig.json
+5
apps/viridian/go.mod
+5
apps/viridian/go.mod
+2
apps/viridian/go.sum
+2
apps/viridian/go.sum
+29
apps/viridian/main.go
+29
apps/viridian/main.go
···
1
+
package main
2
+
3
+
import (
4
+
"fmt"
5
+
"sync"
6
+
7
+
server "viridian/server"
8
+
worker "viridian/worker"
9
+
)
10
+
11
+
func main() {
12
+
var wg sync.WaitGroup
13
+
14
+
// Run the server in a goroutine
15
+
go server.RunServer()
16
+
17
+
// Simulate workers, each running in a separate goroutine
18
+
workerCount := worker.GetCoreCount()
19
+
fmt.Printf("Starting %d workers\n", workerCount)
20
+
for i := 1; i <= workerCount; i++ {
21
+
fmt.Printf("Starting worker %d\n", i)
22
+
workerID := fmt.Sprintf("worker-%d", i)
23
+
wg.Add(1)
24
+
go worker.RunWorker(workerID, &wg)
25
+
}
26
+
27
+
// Wait for all workers to complete (this won't happen in this example since workers run indefinitely)
28
+
wg.Wait()
29
+
}
+11
apps/viridian/package.json
+11
apps/viridian/package.json
+62
apps/viridian/server/server.go
+62
apps/viridian/server/server.go
···
1
+
package server
2
+
3
+
import (
4
+
"fmt"
5
+
"time"
6
+
7
+
"viridian/types"
8
+
9
+
zmq "github.com/pebbe/zmq4"
10
+
)
11
+
12
+
// RunServer launches the server that distributes jobs to workers
13
+
func RunServer() {
14
+
server := &types.Server{
15
+
Jobs: []types.Job{{"job1", "pending"}, {"job2", "pending"}, {"job3", "pending"}},
16
+
WorkerJobs: make(map[string]string),
17
+
JobStatus: make(map[string]string),
18
+
}
19
+
20
+
// Initialize job status
21
+
for _, job := range server.Jobs {
22
+
server.JobStatus[job.ID] = "pending"
23
+
}
24
+
25
+
// Create a ZeroMQ ROUTER socket for distributing jobs
26
+
socket, _ := zmq.NewSocket(zmq.ROUTER)
27
+
defer socket.Close()
28
+
socket.Bind("tcp://*:5555")
29
+
30
+
for len(server.Jobs) > 0 {
31
+
// Wait for a worker to send a ready message
32
+
workerAddr, _ := socket.RecvMessage(0)
33
+
34
+
fmt.Printf("Received ready message from worker %s\n", workerAddr)
35
+
36
+
fmt.Print(workerAddr[2])
37
+
38
+
// Lock jobs to pick the next one safely
39
+
server.Mu.Lock()
40
+
if len(server.Jobs) == 0 {
41
+
server.Mu.Unlock()
42
+
continue
43
+
}
44
+
job := server.Jobs[0]
45
+
server.Jobs = server.Jobs[1:]
46
+
server.Mu.Unlock()
47
+
48
+
// Track the job for this worker
49
+
server.TrackJob(workerAddr[0], job.ID)
50
+
51
+
// Send job to the worker
52
+
fmt.Printf("Sending job %s to worker %s\n", job.ID, workerAddr[0])
53
+
socket.SendMessage(workerAddr[0], "", job.ID)
54
+
55
+
time.Sleep(1 * time.Second) // Simulate a delay
56
+
57
+
// In a real-world scenario, this would be based on a worker response
58
+
server.MarkJobCompleted(workerAddr[0])
59
+
}
60
+
61
+
fmt.Println("All jobs processed")
62
+
}
+46
apps/viridian/types/types.go
+46
apps/viridian/types/types.go
···
1
+
package types
2
+
3
+
import (
4
+
"strconv"
5
+
"sync"
6
+
)
7
+
8
+
// Job represents a job with an ID and status
9
+
type Job struct {
10
+
ID string
11
+
Status string
12
+
}
13
+
14
+
// Server represents the state of the server, including jobs, worker-job associations, and job status
15
+
type Server struct {
16
+
Jobs []Job // Job queue
17
+
WorkerJobs map[string]string // Mapping worker ID -> job ID
18
+
JobStatus map[string]string // Mapping job ID -> status
19
+
Mu sync.Mutex // Mutex to handle concurrency
20
+
}
21
+
22
+
func (s *Server) AddJob() Job {
23
+
s.Mu.Lock()
24
+
defer s.Mu.Unlock()
25
+
job := Job{"job" + strconv.Itoa(len(s.Jobs)), "pending"}
26
+
s.Jobs = append(s.Jobs, job)
27
+
28
+
return job
29
+
}
30
+
31
+
// TrackJob assigns a job to a worker and marks it as in-progress
32
+
func (s *Server) TrackJob(workerID, jobID string) {
33
+
s.Mu.Lock()
34
+
defer s.Mu.Unlock()
35
+
s.WorkerJobs[workerID] = jobID
36
+
s.JobStatus[jobID] = "in-progress"
37
+
}
38
+
39
+
// MarkJobCompleted marks a job as completed and removes the worker-job association
40
+
func (s *Server) MarkJobCompleted(workerID string) {
41
+
s.Mu.Lock()
42
+
defer s.Mu.Unlock()
43
+
jobID := s.WorkerJobs[workerID]
44
+
s.JobStatus[jobID] = "completed"
45
+
delete(s.WorkerJobs, workerID) // Remove worker-job association
46
+
}
+1
apps/viridian/worker/ffmpeg.go
+1
apps/viridian/worker/ffmpeg.go
···
1
+
package worker
+47
apps/viridian/worker/worker.go
+47
apps/viridian/worker/worker.go
···
1
+
package worker
2
+
3
+
import (
4
+
"fmt"
5
+
"runtime"
6
+
"sync"
7
+
"time"
8
+
9
+
zmq "github.com/pebbe/zmq4"
10
+
)
11
+
12
+
// RunWorker simulates a worker that connects to the server and processes jobs
13
+
func RunWorker(id string, wg *sync.WaitGroup) {
14
+
defer wg.Done()
15
+
16
+
// Create a ZeroMQ REQ socket for receiving jobs
17
+
socket, _ := zmq.NewSocket(zmq.REQ)
18
+
defer socket.Close()
19
+
socket.Connect("tcp://localhost:5555")
20
+
21
+
for {
22
+
// Send a ready signal to the server
23
+
socket.SendMessage(fmt.Sprintf("READY %s", id), 0)
24
+
25
+
// Receive the job
26
+
msg, err := socket.RecvMessage(0)
27
+
if err != nil {
28
+
fmt.Println("Error receiving job:", err)
29
+
continue
30
+
}
31
+
job := msg[0]
32
+
33
+
// Process the job
34
+
fmt.Printf("Worker %s received job: %s\n", id, job)
35
+
socket.SendMessage(fmt.Sprintf("RECEIVED %s", job), 0)
36
+
37
+
// Simulate processing the job
38
+
fmt.Printf("Worker %s processing job: %s\n", id, job)
39
+
socket.SendMessage(fmt.Sprintf("PROCESSING %s", job), 0)
40
+
time.Sleep(2 * time.Second) // Simulate job processing delay
41
+
}
42
+
}
43
+
44
+
// GetCoreCount returns the number of CPU cores on the current machine
45
+
func GetCoreCount() int {
46
+
return runtime.NumCPU()
47
+
}
bun.lockb
bun.lockb
This is a binary file and will not be displayed.
+51
compose.dev.yml
+51
compose.dev.yml
···
1
+
services:
2
+
traefik:
3
+
image: traefik:v2.10
4
+
container_name: traefik
5
+
command:
6
+
- "--api.insecure=true"
7
+
- "--providers.file.directory=/etc/traefik/dynamic"
8
+
- "--providers.file.watch=true"
9
+
- "--entrypoints.web.address=:80"
10
+
ports:
11
+
- "80:80" # HTTP
12
+
- "8080:8080" # Dashboard
13
+
volumes:
14
+
- ./traefik/dynamic:/etc/traefik/dynamic:ro
15
+
networks:
16
+
- app_network
17
+
extra_hosts:
18
+
- "host.docker.internal:host-gateway" # This allows reaching host machine
19
+
20
+
postgres:
21
+
image: postgres:latest
22
+
container_name: postgres_db
23
+
environment:
24
+
POSTGRES_USER: postgres
25
+
POSTGRES_PASSWORD: yourpassword
26
+
POSTGRES_DB: yourdatabase
27
+
ports:
28
+
- "5432:5432"
29
+
volumes:
30
+
- postgres_data:/var/lib/postgresql/data
31
+
networks:
32
+
- app_network
33
+
34
+
redis:
35
+
image: redis:latest
36
+
container_name: redis_cache
37
+
ports:
38
+
- "6379:6379"
39
+
volumes:
40
+
- redis_data:/data
41
+
command: redis-server --appendonly yes
42
+
networks:
43
+
- app_network
44
+
45
+
networks:
46
+
app_network:
47
+
driver: bridge
48
+
49
+
volumes:
50
+
postgres_data:
51
+
redis_data:
+49
lexicons/app.bsky.actor.profile.json
+49
lexicons/app.bsky.actor.profile.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.actor.profile",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A declaration of a Bluesky account profile.",
8
+
"key": "literal:self",
9
+
"record": {
10
+
"type": "object",
11
+
"properties": {
12
+
"displayName": {
13
+
"type": "string",
14
+
"maxGraphemes": 64,
15
+
"maxLength": 640
16
+
},
17
+
"description": {
18
+
"type": "string",
19
+
"description": "Free-form profile description text.",
20
+
"maxGraphemes": 256,
21
+
"maxLength": 2560
22
+
},
23
+
"avatar": {
24
+
"type": "blob",
25
+
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
26
+
"accept": ["image/png", "image/jpeg"],
27
+
"maxSize": 1000000
28
+
},
29
+
"banner": {
30
+
"type": "blob",
31
+
"description": "Larger horizontal image to display behind profile view.",
32
+
"accept": ["image/png", "image/jpeg"],
33
+
"maxSize": 1000000
34
+
},
35
+
"labels": {
36
+
"type": "union",
37
+
"description": "Self-label values, specific to the Bluesky application, on the overall account.",
38
+
"refs": ["com.atproto.label.defs#selfLabels"]
39
+
},
40
+
"joinedViaStarterPack": {
41
+
"type": "ref",
42
+
"ref": "com.atproto.repo.strongRef"
43
+
},
44
+
"createdAt": { "type": "string", "format": "datetime" }
45
+
}
46
+
}
47
+
}
48
+
}
49
+
}
+23
lexicons/xyz.statusphere.status.json
+23
lexicons/xyz.statusphere.status.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "xyz.statusphere.status",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"key": "tid",
8
+
"record": {
9
+
"type": "object",
10
+
"required": ["status", "createdAt"],
11
+
"properties": {
12
+
"status": {
13
+
"type": "string",
14
+
"minLength": 1,
15
+
"maxGraphemes": 1,
16
+
"maxLength": 32
17
+
},
18
+
"createdAt": { "type": "string", "format": "datetime" }
19
+
}
20
+
}
21
+
}
22
+
}
23
+
}
+16
-9
package.json
+16
-9
package.json
···
1
1
{
2
2
"name": "teal",
3
-
"version": "1.0.50",
3
+
"private": true,
4
+
"version": "0.0.0",
5
+
"packageManager": "bun@1.0.0+",
4
6
"scripts": {
5
-
"test": "echo \"Error: no test specified\" && exit 1",
6
-
"dev": "bun run --watch src/index.ts"
7
+
"install": "turbo run install",
8
+
"build": "turbo run build",
9
+
"dev": "turbo run dev",
10
+
"lint": "turbo run lint"
7
11
},
8
-
"dependencies": {
9
-
"elysia": "latest"
12
+
"devDependencies": {
13
+
"turbo": "latest"
10
14
},
11
-
"devDependencies": {
12
-
"bun-types": "latest"
15
+
"engines": {
16
+
"node": ">=20.0.0"
13
17
},
14
-
"module": "src/index.js"
15
-
}
18
+
"workspaces": [
19
+
"apps/*",
20
+
"packages/*"
21
+
]
22
+
}
-7
src/index.ts
-7
src/index.ts
+22
traefik/dev_routes.yml
+22
traefik/dev_routes.yml
···
1
+
http:
2
+
routers:
3
+
frontend:
4
+
rule: "PathPrefix(`/`)"
5
+
service: frontend
6
+
priority: 1
7
+
8
+
backend:
9
+
rule: "PathPrefix(`/api`)"
10
+
service: backend
11
+
priority: 2
12
+
13
+
services:
14
+
frontend:
15
+
loadBalancer:
16
+
servers:
17
+
- url: "http://host.docker.internal:3000"
18
+
19
+
backend:
20
+
loadBalancer:
21
+
servers:
22
+
- url: "http://host.docker.internal:3031"
+24
traefik/dynamic_conf.yml
+24
traefik/dynamic_conf.yml
···
1
+
http:
2
+
middlewares:
3
+
security-headers:
4
+
headers:
5
+
frameDeny: true
6
+
sslRedirect: true
7
+
browserXssFilter: true
8
+
contentTypeNosniff: true
9
+
forceSTSHeader: true
10
+
stsIncludeSubdomains: true
11
+
stsPreload: true
12
+
stsSeconds: 31536000
13
+
14
+
cors-headers:
15
+
headers:
16
+
accessControlAllowMethods:
17
+
- GET
18
+
- POST
19
+
- PUT
20
+
- DELETE
21
+
- OPTIONS
22
+
accessControlAllowHeaders:
23
+
- "*"
24
+
accessControlAllowOrigin: "*"
+56
traefik/traefik.yml
+56
traefik/traefik.yml
···
1
+
api:
2
+
insecure: true
3
+
dashboard: true
4
+
5
+
entryPoints:
6
+
web:
7
+
address: ":80"
8
+
http:
9
+
redirections:
10
+
entryPoint:
11
+
to: websecure
12
+
scheme: https
13
+
14
+
websecure:
15
+
address: ":443"
16
+
17
+
providers:
18
+
docker:
19
+
endpoint: "unix:///var/run/docker.sock"
20
+
exposedByDefault: false
21
+
network: app_network
22
+
23
+
file:
24
+
filename: /etc/traefik/dynamic_conf.yml
25
+
26
+
certificatesResolvers:
27
+
letsencrypt:
28
+
acme:
29
+
email: your-email@domain.com
30
+
storage: /letsencrypt/acme.json
31
+
httpChallenge:
32
+
entryPoint: web
33
+
http:
34
+
middlewares:
35
+
security-headers:
36
+
headers:
37
+
frameDeny: true
38
+
sslRedirect: true
39
+
browserXssFilter: true
40
+
contentTypeNosniff: true
41
+
forceSTSHeader: true
42
+
stsIncludeSubdomains: true
43
+
stsPreload: true
44
+
stsSeconds: 31536000
45
+
46
+
cors-headers:
47
+
headers:
48
+
accessControlAllowMethods:
49
+
- GET
50
+
- POST
51
+
- PUT
52
+
- DELETE
53
+
- OPTIONS
54
+
accessControlAllowHeaders:
55
+
- "*"
56
+
accessControlAllowOrigin: "*"
+9
-9
tsconfig.json
packages/tsconfig/base.json
+9
-9
tsconfig.json
packages/tsconfig/base.json
···
11
11
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12
12
13
13
/* Language and Environment */
14
-
"target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
14
+
"target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15
15
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16
16
// "jsx": "preserve", /* Specify what JSX code is generated. */
17
17
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
···
25
25
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26
26
27
27
/* Modules */
28
-
"module": "ES2022", /* Specify what module code is generated. */
28
+
"module": "ES2022" /* Specify what module code is generated. */,
29
29
// "rootDir": "./", /* Specify the root folder within your source files. */
30
-
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30
+
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
31
31
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32
-
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32
+
//"paths": {} /* Specify a set of entries that re-map imports to additional lookup locations. */,
33
33
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34
34
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35
-
"types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */
35
+
// "types": [] /* Specify type package names to be included without being referenced in a source file. */,
36
36
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37
37
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38
38
// "resolveJsonModule": true, /* Enable importing .json files. */
···
71
71
/* Interop Constraints */
72
72
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73
73
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74
-
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
74
+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
75
75
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76
-
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
76
+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
77
77
78
78
/* Type Checking */
79
-
"strict": true, /* Enable all strict type-checking options. */
79
+
"strict": true /* Enable all strict type-checking options. */,
80
80
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81
81
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82
82
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
···
98
98
99
99
/* Completeness */
100
100
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101
-
"skipLibCheck": true /* Skip type checking all .d.ts files. */
101
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
102
102
}
103
103
}
+19
turbo.json
+19
turbo.json
···
1
+
{
2
+
"$schema": "https://turbo.build/schema.json",
3
+
"tasks": {
4
+
"install": {
5
+
"dependsOn": ["^install"]
6
+
},
7
+
"build": {
8
+
"dependsOn": ["^build"],
9
+
"outputs": [".next/**", "!.next/cache/**"]
10
+
},
11
+
"check-types": {
12
+
"dependsOn": ["^check-types"]
13
+
},
14
+
"dev": {
15
+
"persistent": true,
16
+
"cache": false
17
+
}
18
+
}
19
+
}