Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

Merge branch 'master' into master

authored by

natalie and committed by
GitHub
18ad5a3e d929efe4

+832 -261
+1
.gitignore
··· 25 25 yarn-error.log* 26 26 27 27 # local env files 28 + .env 28 29 .env.local 29 30 .env.development.local 30 31 .env.test.local
apps/aqua/.drizzle/0000_same_maelstrom.sql packages/db/.drizzle/0000_same_maelstrom.sql
apps/aqua/.drizzle/meta/0000_snapshot.json packages/db/.drizzle/meta/0000_snapshot.json
-13
apps/aqua/.drizzle/meta/_journal.json
··· 1 - { 2 - "version": "7", 3 - "dialect": "sqlite", 4 - "entries": [ 5 - { 6 - "idx": 0, 7 - "version": "6", 8 - "when": 1729467718506, 9 - "tag": "0000_same_maelstrom", 10 - "breakpoints": true 11 - } 12 - ] 13 - }
-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/db.sqlite

This is a binary file and will not be displayed.

-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 - });
+2 -3
apps/aqua/drizzle.config.ts drizzle.config.ts
··· 1 1 import { defineConfig } from "drizzle-kit"; 2 - import process from "node:process"; 3 2 4 3 export default defineConfig({ 5 4 dialect: "sqlite", 6 - schema: "./db/schema.ts", 7 - out: "./.drizzle", 5 + schema: "./packages/db/schema.ts", 6 + out: "./packages/db/.drizzle", 8 7 casing: "snake_case", 9 8 dbCredentials: { 10 9 url: process.env.DATABASE_URL ?? "./db.sqlite",
+5 -3
apps/aqua/package.json
··· 28 28 "@hono/node-server": "^1.13.4", 29 29 "@libsql/client": "^0.14.0", 30 30 "dotenv": "^16.4.5", 31 - "drizzle-orm": "^0.35.3", 31 + "drizzle-orm": "^0.36", 32 32 "envalid": "^8.0.0", 33 33 "hono": "^4.6.8", 34 34 "jose": "^5.9.6", 35 35 "pino": "^9.5.0", 36 36 "turbo": "^2.2.3", 37 - "uhtml": "^4.5.11" 37 + "uhtml": "^4.5.11", 38 + "@teal/db": "*", 39 + "@teal/lexicons": "*" 38 40 }, 39 41 "devDependencies": { 40 42 "@teal/tsconfig": "*", 41 43 "@atproto/lex-cli": "^0.4.1", 42 44 "@types/node": "^20.17.5", 43 - "drizzle-kit": "^0.26.2", 45 + "drizzle-kit": "^0.27", 44 46 "pino-pretty": "^11.3.0", 45 47 "rimraf": "^6.0.1", 46 48 "tsup": "^8.3.5",
-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);
+26 -10
apps/aqua/src/auth/router.ts
··· 1 1 import { createClient } from "./client"; 2 2 import { db } from "@/db"; 3 3 import { EnvWithCtx, TealContext } from "@/ctx"; 4 - import { authSession } from "../../db/schema"; 5 4 import { Hono } from "hono"; 5 + import { tealSession } from "@teal/db/schema"; 6 + 7 + import { setCookie } from "hono/cookie"; 8 + import { env } from "@/lib/env"; 6 9 7 10 interface LoginBody { 8 11 handle?: string; ··· 52 55 const params = new URLSearchParams(honoParams); 53 56 const cb = await auth.callback(params); 54 57 55 - // generate a session key (random) 58 + let did = cb.session.did; 59 + // gen opaque tealSessionKey 60 + const sess = crypto.randomUUID(); 61 + await db 62 + .insert(tealSession) 63 + .values({ 64 + key: sess, 65 + // ATP session key (DID) 66 + session: JSON.stringify(cb.session.did), 67 + provider: "atproto", 68 + }) 69 + .execute(); 56 70 57 - const sessionKey = crypto.randomUUID(); 58 - 59 - // insert in session table, return data from cb 60 - 61 - c.var.db.insert(authSession).values({ 62 - key: sessionKey, 63 - session: JSON.stringify(cb.session), 71 + // cookie time 72 + console.log("Setting cookie", sess); 73 + setCookie(c, "tealSession", "teal:" + sess, { 74 + httpOnly: true, 75 + secure: env.HOST.startsWith("https"), 76 + sameSite: "lax", 77 + path: "/", 78 + maxAge: 60 * 60 * 24 * 365, 64 79 }); 65 80 66 - return Response.json(cb); 81 + return Response.json({ did, sessionId: sess }); 67 82 } catch (e) { 68 83 console.error(e); 69 84 return Response.json({ error: "Could not authorize user" }); ··· 71 86 } 72 87 73 88 const app = new Hono<EnvWithCtx>(); 89 + 74 90 75 91 app.get("/login/:handle", async (c) => loginGet(c)); 76 92
+10 -14
apps/aqua/src/auth/storage.ts
··· 5 5 NodeSavedStateStore, 6 6 } from "@atproto/oauth-client-node"; 7 7 import type { Database } from "@/db"; 8 - import { authSession, authState } from "../../db/schema"; 8 + import { atProtoSession, authState } from "@teal/db/schema"; 9 9 import { eq } from "drizzle-orm"; 10 10 11 11 export class StateStore implements NodeSavedStateStore { ··· 52 52 async get(key: string): Promise<NodeSavedSession | undefined> { 53 53 const result = await this.db 54 54 .select() 55 - .from(authSession) 56 - .where(eq(authSession.key, key)) 55 + .from(atProtoSession) 56 + .where(eq(atProtoSession.key, key)) 57 57 .limit(1) 58 58 .all(); 59 - // .selectFrom("auth_session") 60 - // .selectAll() 61 - // .where("key", "=", key) 62 - // .executeTakeFirst(); 63 59 if (!result[0]) return; 64 60 return JSON.parse(result[0].session) as NodeSavedSession; 65 61 } 66 62 async set(key: string, val: NodeSavedSession) { 67 63 const session = JSON.stringify(val); 64 + console.log("inserting session", key, session); 68 65 await this.db 69 - .insert(authSession) 66 + .insert(atProtoSession) 70 67 .values({ key, session }) 71 68 .onConflictDoUpdate({ 72 69 set: { session: session }, 73 - target: authSession.key, 70 + target: atProtoSession.key, 74 71 }) 75 72 .execute(); 76 - // .insertInto("auth_session") 77 - // .values({ key, session }) 78 - // .onConflict((oc) => oc.doUpdateSet({ session })) 79 - // .execute(); 80 73 } 81 74 async del(key: string) { 82 - await this.db.delete(authSession).where(eq(authSession.key, key)).execute(); 75 + await this.db 76 + .delete(atProtoSession) 77 + .where(eq(atProtoSession.key, key)) 78 + .execute(); 83 79 //.deleteFrom("auth_session").where("key", "=", key).execute(); 84 80 } 85 81 }
+2 -2
apps/aqua/src/ctx.ts
··· 13 13 14 14 export type Ctx = { 15 15 auth: NodeOAuthClient; 16 - db: LibSQLDatabase<typeof import("/Users/natalie/Code/teal/db/schema")> & { 16 + db: LibSQLDatabase<typeof import("@teal/db/schema")> & { 17 17 $client: Client; 18 18 }; 19 19 logger: Logger<never, boolean>; ··· 21 21 22 22 export const setupContext = async ( 23 23 c: TealContext, 24 - db: LibSQLDatabase<typeof import("/Users/natalie/Code/teal/db/schema")> & { 24 + db: LibSQLDatabase<typeof import("@teal/db/schema")> & { 25 25 $client: Client; 26 26 }, 27 27 logger: Logger<never, boolean>,
+11 -2
apps/aqua/src/db.ts
··· 1 1 import { drizzle } from "drizzle-orm/libsql"; 2 - import * as schema from "../db/schema"; 2 + import * as schema from "@teal/db/schema"; 3 3 import process from "node:process"; 4 + import path from "node:path"; 5 + 6 + console.log( 7 + "Loading SQLite file at", 8 + path.join(process.cwd(), "../../db.sqlite"), 9 + ); 4 10 5 11 export const db = drizzle({ 6 - connection: process.env.DATABASE_URL ?? "file:./db.sqlite", 12 + connection: 13 + // default is in project root / db.sqlite 14 + process.env.DATABASE_URL ?? 15 + "file:" + path.join(process.cwd(), "../../db.sqlite"), 7 16 // doesn't seem to work? 8 17 //casing: "snake_case", 9 18 schema: schema,
+15 -15
apps/aqua/src/index.ts
··· 22 22 23 23 app.route("/oauth", getAuthRouter()); 24 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 - }; 25 + const run = async () => { 26 + serveNode( 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 39 40 - run(); 40 + run(); 41 41 42 42 export default app;
apps/aqua/src/lexicon/index.ts packages/lexicons/generated/server/index.ts
apps/aqua/src/lexicon/lexicons.ts packages/lexicons/generated/server/lexicons.ts
apps/aqua/src/lexicon/types/app/bsky/actor/profile.ts packages/lexicons/generated/server/types/app/bsky/actor/profile.ts
apps/aqua/src/lexicon/types/xyz/statusphere/status.ts packages/lexicons/generated/server/types/xyz/statusphere/status.ts
apps/aqua/src/lexicon/util.ts packages/lexicons/generated/server/util.ts
+120
apps/aqua/src/lib/auth.ts
··· 1 + import { Ctx, EnvWithCtx } from "@/ctx"; 2 + import { db } from "@/db"; 3 + import { Agent, lexicons } from "@atproto/api"; 4 + import { NodeOAuthClient, Session } from "@atproto/oauth-client-node"; 5 + import { tealSession } from "@teal/db/schema"; 6 + import { eq } from "drizzle-orm"; 7 + import { Context } from "hono"; 8 + import { getCookie } from "hono/cookie"; 9 + 10 + interface UserSession { 11 + did: string; 12 + /// The session JWT from ATProto 13 + session: Session; 14 + } 15 + 16 + interface UserInfo { 17 + did: string; 18 + handle: string; 19 + } 20 + 21 + export async function getSessionAgent( 22 + c: Context<EnvWithCtx>, 23 + did: string, 24 + ): Promise<Agent> { 25 + const session = await getSession(c); 26 + const auth = c.get("auth"); 27 + try { 28 + const session = await auth.restore(did); 29 + if (session) { 30 + return new Agent(session); 31 + } 32 + throw new Error("Failed to restore session"); 33 + } catch (e) { 34 + console.error(e); 35 + throw new Error("Failed to restore session" + e); 36 + } 37 + } 38 + 39 + export async function getUserInfo( 40 + c: Context<EnvWithCtx>, 41 + did: string, 42 + ): Promise<UserInfo> { 43 + // init session agent 44 + const agent = await getSessionAgent(c, did); 45 + // fetch from ATProto 46 + const res = await agent.app.bsky.actor.getProfile({ 47 + actor: did, 48 + }); 49 + if (res.success) { 50 + return { 51 + did, 52 + handle: res.data.handle, 53 + email: res.data.email, 54 + }; 55 + } else { 56 + throw new Error("Failed to fetch user info"); 57 + } 58 + } 59 + 60 + /** 61 + * Get the auth session from the request cookie or Authorization header 62 + */ 63 + export async function getAuthSession(c: Context<EnvWithCtx>): Promise<Session> { 64 + let authSession = getCookie(c, "authSession"); 65 + if (!authSession) { 66 + authSession = c.req.header("Authorization"); 67 + } 68 + if (!authSession) { 69 + throw new Error("No auth session found"); 70 + } else { 71 + // get the DID from the session 72 + const did = await db 73 + .select() 74 + .from(tealSession) 75 + .where(eq(tealSession.key, authSession)) 76 + .limit(1) 77 + .all() 78 + .then((result) => result[0]?.session); 79 + if (!did) { 80 + throw new Error("No DID found in session"); 81 + } 82 + return getATPAuthSession(c, did); 83 + } 84 + } 85 + 86 + export async function getSession(c: Context<EnvWithCtx>): Promise<Session> { 87 + let authSession = getCookie(c, "authSession"); 88 + if (!authSession) { 89 + authSession = c.req.header("Authorization"); 90 + } 91 + if (!authSession) { 92 + throw new Error("No auth session found"); 93 + } else { 94 + // get the DID from the session 95 + const did = await db 96 + .select() 97 + .from(tealSession) 98 + .where(eq(tealSession.key, authSession)) 99 + .limit(1) 100 + .all() 101 + .then((result) => result[0]?.session); 102 + if (!did) { 103 + throw new Error("No DID found in session"); 104 + } 105 + return getATPAuthSession(c, did); 106 + } 107 + } 108 + 109 + // get the auth session from cookie or Authorization header 110 + export async function getATPAuthSession( 111 + c: Context<EnvWithCtx>, 112 + did: string, 113 + ): Promise<Session> { 114 + let auth: NodeOAuthClient = c.get("auth"); 115 + const jwt = await auth.sessionGetter.get(did); 116 + if (jwt) { 117 + return jwt; 118 + } 119 + throw new Error("No auth session found"); 120 + }
+2
apps/aqua/src/lib/env.ts
··· 3 3 import process from "node:process"; 4 4 5 5 dotenv.config(); 6 + // in case our .env file is in the root folder 7 + dotenv.config({ path: "./../../.env" }); 6 8 7 9 export const env = cleanEnv(process.env, { 8 10 NODE_ENV: str({
+42
apps/aqua/src/lib/idResolver.ts
··· 1 + // Stolen from https://github.com/bluesky-social/statusphere-example-app/blob/main/src/id-resolver.ts 2 + import { IdResolver, MemoryCache } from "@atproto/identity"; 3 + 4 + const HOUR = 60e3 * 60; 5 + const DAY = HOUR * 24; 6 + 7 + export function createIdResolver() { 8 + return new IdResolver({ 9 + didCache: new MemoryCache(HOUR, DAY), 10 + }); 11 + } 12 + 13 + export interface BidirectionalResolver { 14 + resolveDidToHandle(did: string): Promise<string>; 15 + resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>; 16 + } 17 + 18 + export function createBidirectionalResolver(resolver: IdResolver) { 19 + return { 20 + async resolveDidToHandle(did: string): Promise<string> { 21 + const didDoc = await resolver.did.resolveAtprotoData(did); 22 + const resolvedHandle = await resolver.handle.resolve(didDoc.handle); 23 + if (resolvedHandle === did) { 24 + return didDoc.handle; 25 + } 26 + return did; 27 + }, 28 + 29 + async resolveDidsToHandles( 30 + dids: string[], 31 + ): Promise<Record<string, string>> { 32 + const didHandleMap: Record<string, string> = {}; 33 + const resolves = await Promise.all( 34 + dids.map((did) => this.resolveDidToHandle(did).catch((_) => did)), 35 + ); 36 + for (let i = 0; i < dids.length; i++) { 37 + didHandleMap[dids[i]] = resolves[i]; 38 + } 39 + return didHandleMap; 40 + }, 41 + }; 42 + }
+21
apps/aqua/src/status.ts
··· 1 + import { Hono } from "hono"; 2 + import { EnvWithCtx } from "./ctx"; 3 + 4 + const app = new Hono<EnvWithCtx>(); 5 + 6 + app.get("/me", async (c) => { 7 + getSession(c); 8 + }); 9 + 10 + app.post("/status", async (c) => { 11 + const result = await c.db.query.status.insert({ 12 + uri: c.req.body.uri, 13 + authorDid: c.req.body.authorDid, 14 + status: c.req.body.status, 15 + createdAt: new Date().toISOString(), 16 + indexedAt: new Date().toISOString(), 17 + }); 18 + return c.json(result); 19 + }); 20 + 21 + export default app;
+2 -1
apps/aqua/tsconfig.json
··· 2 2 "extends": "@teal/tsconfig/base.json", 3 3 "compilerOptions": { 4 4 "paths": { 5 - "@/*": ["./src/*"] 5 + "@/*": ["./src/*"], 6 + "+lexicons/*": ["../../lexicons/*"] 6 7 } 7 8 } 8 9 }
bun.lockb

This is a binary file and will not be displayed.

db.sqlite

This is a binary file and will not be displayed.

lexicons/app.bsky.actor.profile.json packages/lexicons/src/app.bsky.actor.profile.json
lexicons/xyz.statusphere.status.json packages/lexicons/src/xyz.statusphere.status.json
+7 -1
package.json
··· 7 7 "install": "turbo run install", 8 8 "build": "turbo run build", 9 9 "dev": "turbo run dev", 10 - "lint": "turbo run lint" 10 + "lint": "turbo run lint", 11 + "lex:gen-server": "lex gen-server ./packages/lexicons/generated/server ./packages/lexicons/src/*", 12 + "lex:gen-api": "lex gen-api ./packages/lexicons/generated/api ./packages/lexicons/src/*", 13 + "lex:gen-md": "lex gen-md ./packages/lexicons/generated/md ./packages/lexicons/src/*", 14 + "db:studio": "drizzle-kit studio", 15 + "db:migrate": "drizzle-kit migrate", 16 + "db:seed": "drizzle-kit seed" 11 17 }, 12 18 "devDependencies": { 13 19 "turbo": "latest"
+3
packages/db/.drizzle/0001_fresh_tana_nile.sql
··· 1 + ALTER TABLE `status` RENAME COLUMN "authorDid" TO "author_did";--> statement-breakpoint 2 + ALTER TABLE `status` RENAME COLUMN "createdAt" TO "created_at";--> statement-breakpoint 3 + ALTER TABLE `status` RENAME COLUMN "indexedAt" TO "indexed_at";
+1
packages/db/.drizzle/0002_moaning_roulette.sql
··· 1 + ALTER TABLE `auth_session` RENAME TO `atp_session`;
+12
packages/db/.drizzle/0003_sharp_medusa.sql
··· 1 + CREATE TABLE `teal_session` ( 2 + `key` text PRIMARY KEY NOT NULL, 3 + `session` text NOT NULL, 4 + `provider` text NOT NULL 5 + ); 6 + --> statement-breakpoint 7 + CREATE TABLE `teal_user` ( 8 + `did` text PRIMARY KEY NOT NULL, 9 + `handle` text NOT NULL, 10 + `email` text NOT NULL, 11 + `created_at` text NOT NULL 12 + );
+115
packages/db/.drizzle/meta/0001_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "0668ebf0-fa9b-41bc-90fa-f25992bf4b76", 5 + "prevId": "417cce08-b23b-4a9f-ad7e-e96215e0fb38", 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 + "author_did": { 66 + "name": "author_did", 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 + "created_at": { 80 + "name": "created_at", 81 + "type": "text", 82 + "primaryKey": false, 83 + "notNull": true, 84 + "autoincrement": false 85 + }, 86 + "indexed_at": { 87 + "name": "indexed_at", 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 + "\"status\".\"authorDid\"": "\"status\".\"author_did\"", 108 + "\"status\".\"createdAt\"": "\"status\".\"created_at\"", 109 + "\"status\".\"indexedAt\"": "\"status\".\"indexed_at\"" 110 + } 111 + }, 112 + "internal": { 113 + "indexes": {} 114 + } 115 + }
+113
packages/db/.drizzle/meta/0002_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "1123b2f8-11c2-4d28-925a-f7a30bb3bec6", 5 + "prevId": "0668ebf0-fa9b-41bc-90fa-f25992bf4b76", 6 + "tables": { 7 + "atp_session": { 8 + "name": "atp_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 + "author_did": { 66 + "name": "author_did", 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 + "created_at": { 80 + "name": "created_at", 81 + "type": "text", 82 + "primaryKey": false, 83 + "notNull": true, 84 + "autoincrement": false 85 + }, 86 + "indexed_at": { 87 + "name": "indexed_at", 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 + "\"auth_session\"": "\"atp_session\"" 107 + }, 108 + "columns": {} 109 + }, 110 + "internal": { 111 + "indexes": {} 112 + } 113 + }
+180
packages/db/.drizzle/meta/0003_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "7710000b-44fd-4d23-a768-0117f22926c3", 5 + "prevId": "1123b2f8-11c2-4d28-925a-f7a30bb3bec6", 6 + "tables": { 7 + "atp_session": { 8 + "name": "atp_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 + "author_did": { 66 + "name": "author_did", 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 + "created_at": { 80 + "name": "created_at", 81 + "type": "text", 82 + "primaryKey": false, 83 + "notNull": true, 84 + "autoincrement": false 85 + }, 86 + "indexed_at": { 87 + "name": "indexed_at", 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 + "teal_session": { 101 + "name": "teal_session", 102 + "columns": { 103 + "key": { 104 + "name": "key", 105 + "type": "text", 106 + "primaryKey": true, 107 + "notNull": true, 108 + "autoincrement": false 109 + }, 110 + "session": { 111 + "name": "session", 112 + "type": "text", 113 + "primaryKey": false, 114 + "notNull": true, 115 + "autoincrement": false 116 + }, 117 + "provider": { 118 + "name": "provider", 119 + "type": "text", 120 + "primaryKey": false, 121 + "notNull": true, 122 + "autoincrement": false 123 + } 124 + }, 125 + "indexes": {}, 126 + "foreignKeys": {}, 127 + "compositePrimaryKeys": {}, 128 + "uniqueConstraints": {}, 129 + "checkConstraints": {} 130 + }, 131 + "teal_user": { 132 + "name": "teal_user", 133 + "columns": { 134 + "did": { 135 + "name": "did", 136 + "type": "text", 137 + "primaryKey": true, 138 + "notNull": true, 139 + "autoincrement": false 140 + }, 141 + "handle": { 142 + "name": "handle", 143 + "type": "text", 144 + "primaryKey": false, 145 + "notNull": true, 146 + "autoincrement": false 147 + }, 148 + "email": { 149 + "name": "email", 150 + "type": "text", 151 + "primaryKey": false, 152 + "notNull": true, 153 + "autoincrement": false 154 + }, 155 + "created_at": { 156 + "name": "created_at", 157 + "type": "text", 158 + "primaryKey": false, 159 + "notNull": true, 160 + "autoincrement": false 161 + } 162 + }, 163 + "indexes": {}, 164 + "foreignKeys": {}, 165 + "compositePrimaryKeys": {}, 166 + "uniqueConstraints": {}, 167 + "checkConstraints": {} 168 + } 169 + }, 170 + "views": {}, 171 + "enums": {}, 172 + "_meta": { 173 + "schemas": {}, 174 + "tables": {}, 175 + "columns": {} 176 + }, 177 + "internal": { 178 + "indexes": {} 179 + } 180 + }
+34
packages/db/.drizzle/meta/_journal.json
··· 1 + { 2 + "version": "7", 3 + "dialect": "sqlite", 4 + "entries": [ 5 + { 6 + "idx": 0, 7 + "version": "6", 8 + "when": 1729467718506, 9 + "tag": "0000_same_maelstrom", 10 + "breakpoints": true 11 + }, 12 + { 13 + "idx": 1, 14 + "version": "6", 15 + "when": 1730659321301, 16 + "tag": "0001_fresh_tana_nile", 17 + "breakpoints": true 18 + }, 19 + { 20 + "idx": 2, 21 + "version": "6", 22 + "when": 1730659385717, 23 + "tag": "0002_moaning_roulette", 24 + "breakpoints": true 25 + }, 26 + { 27 + "idx": 3, 28 + "version": "6", 29 + "when": 1731093709171, 30 + "tag": "0003_sharp_medusa", 31 + "breakpoints": true 32 + } 33 + ] 34 + }
packages/db/db.sqlite

This is a binary file and will not be displayed.

+8
packages/db/package.json
··· 1 + { 2 + "name": "@teal/db", 3 + "type": "module", 4 + "dependencies": { 5 + "drizzle-kit": "^0.27.1", 6 + "drizzle-orm": "^0.36.0" 7 + } 8 + }
+72
packages/db/schema.ts
··· 1 + import { sqliteTable, text } from "drizzle-orm/sqlite-core"; 2 + 3 + export type DatabaseSchema = { 4 + status: Status; 5 + auth_session: AuthSession; 6 + auth_state: AuthState; 7 + }; 8 + 9 + export type Status = { 10 + uri: string; 11 + authorDid: string; 12 + status: string; 13 + createdAt: string; 14 + indexedAt: string; 15 + }; 16 + 17 + export type AuthSession = { 18 + key: string; 19 + session: AuthSessionJson; 20 + }; 21 + 22 + export type AuthState = { 23 + key: string; 24 + state: AuthStateJson; 25 + }; 26 + 27 + type AuthStateJson = string; 28 + 29 + type AuthSessionJson = string; 30 + 31 + // Tables 32 + 33 + export const status = sqliteTable("status", { 34 + uri: text().primaryKey(), 35 + authorDid: text().notNull(), 36 + status: text().notNull(), 37 + createdAt: text().notNull(), 38 + indexedAt: text().notNull(), 39 + }); 40 + 41 + // ATP Auth Tables (oAuth) 42 + export const atProtoSession = sqliteTable("atp_session", { 43 + key: text().primaryKey(), 44 + session: text().notNull(), 45 + }); 46 + 47 + export const authState = sqliteTable("auth_state", { 48 + key: text().primaryKey(), 49 + state: text().notNull(), 50 + }); 51 + 52 + export const tealSession = sqliteTable("teal_session", { 53 + key: text().primaryKey(), 54 + session: text().notNull(), 55 + provider: text().notNull(), 56 + }); 57 + 58 + // Regular Auth Tables 59 + export const tealUser = sqliteTable("teal_user", { 60 + did: text().primaryKey(), 61 + handle: text().notNull(), 62 + avatar: text().notNull(), 63 + bio: text(), 64 + createdAt: text().notNull(), 65 + }); 66 + 67 + // follow relationship 68 + export const follow = sqliteTable("follow", { 69 + follower: text().primaryKey(), 70 + followed: text().primaryKey(), 71 + createdAt: text().notNull(), 72 + });
+4
packages/db/tsconfig.json
··· 1 + { 2 + "extends": "@teal/tsconfig/base.json", 3 + "include": ["**/*.*", "./schema.ts"] 4 + }
+8
packages/lexicons/package.json
··· 1 + { 2 + "name": "@teal/lexicons", 3 + "type": "module", 4 + "dependencies": { 5 + "@atproto/lexicon": "^0.4.2", 6 + "@atproto/xrpc-server": "^0.6.4" 7 + } 8 + }
+4
packages/lexicons/tsconfig.json
··· 1 + { 2 + "extends": "@teal/tsconfig/base.json", 3 + "include": ["./generated/**/*.ts"] 4 + }
+10 -99
packages/tsconfig/base.json
··· 1 1 { 2 + "$schema": "https://json.schemastore.org/tsconfig", 2 3 "compilerOptions": { 3 - /* Visit https://aka.ms/tsconfig to read more about this file */ 4 - 5 - /* Projects */ 6 - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 - 13 - /* Language and Environment */ 14 - "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 - // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 - 27 - /* Modules */ 28 - "module": "ES2022" /* Specify what module code is generated. */, 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. */, 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. */, 33 - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 - // "types": [] /* Specify type package names to be included without being referenced in a source file. */, 36 - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 - // "resolveJsonModule": true, /* Enable importing .json files. */ 39 - // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ 40 - 41 - /* JavaScript Support */ 42 - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 - 46 - /* Emit */ 47 - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 - // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 - // "removeComments": true, /* Disable emitting comments. */ 54 - // "noEmit": true, /* Disable emitting files from a compilation. */ 55 - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 - // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 - 71 - /* Interop Constraints */ 72 - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 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. */, 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. */, 77 - 78 - /* Type Checking */ 79 - "strict": true /* Enable all strict type-checking options. */, 80 - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 - 99 - /* Completeness */ 100 - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 - "skipLibCheck": true /* Skip type checking all .d.ts files. */ 4 + "target": "ES2021", 5 + "module": "ESNext", 6 + "moduleResolution": "node", 7 + "esModuleInterop": true, 8 + "forceConsistentCasingInFileNames": true, 9 + "composite": true, 10 + "declarationMap": true, 11 + "strict": true, 12 + "skipLibCheck": true 102 13 } 103 14 }
+2 -1
packages/tsconfig/package.json
··· 1 1 { 2 - "name": "@teal/tsconfig" 2 + "name": "@teal/tsconfig", 3 + "type": "module" 3 4 }