The recipes.blue monorepo recipes.blue
recipes appview atproto

feat: migrate to postgres

hayden.moe 2d0c405e 76c2a290

verified
+1 -2
.env.example
··· 1 1 # Database 2 - DATABASE_URL=http://localhost:4001 3 - DATABASE_AUTH_TOKEN= 2 + DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres 4 3 5 4 # API 6 5 PORT=3000
+3 -1
apps/api/package.json
··· 4 4 "private": true, 5 5 "scripts": { 6 6 "build": "bun --bun run check-types && bun --bun run compile", 7 - "dev": "bun run --hot src/index.ts", 7 + "dev": "bun run --hot src/index.ts | pino-pretty", 8 8 "check-types": "tsc --noEmit", 9 9 "compile": "bun build src/index.ts --compile --minify --sourcemap --outfile=dist/api --target=bun", 10 10 "clean": "rimraf dist" ··· 20 20 "@cookware/lexicons": "workspace:*", 21 21 "@libsql/client": "^0.14.0", 22 22 "drizzle-orm": "catalog:", 23 + "hono": "^4.10.7", 23 24 "pino": "^9.5.0" 24 25 }, 25 26 "devDependencies": { ··· 27 28 "@cookware/tsconfig": "workspace:*", 28 29 "@types/bun": "catalog:", 29 30 "drizzle-kit": "^0.29.0", 31 + "pino-pretty": "^13.1.2", 30 32 "rimraf": "^6.0.1" 31 33 } 32 34 }
+15 -6
apps/api/src/index.ts
··· 6 6 import pino from 'pino'; 7 7 import { RedisClient } from 'bun'; 8 8 import { registerGetProfile } from './xrpc/blue.recipes.actor.getProfile.js'; 9 + import { Hono } from 'hono'; 10 + import { mountXrpcRouter } from './util/hono.js'; 9 11 10 12 const logger = pino(); 11 13 const redis = new RedisClient(Bun.env.REDIS_URL ?? "redis://127.0.0.1:6379/0"); 12 14 13 - const router = new XRPCRouter({ 15 + const xrpcRouter = new XRPCRouter({ 14 16 handleException: (err, _req) => { 15 17 if (err instanceof XRPCError) { 16 18 return err.toResponse(); ··· 34 36 }); 35 37 36 38 // actor 37 - registerGetProfile(router, logger, redis); 39 + registerGetProfile(xrpcRouter, logger, redis); 38 40 39 41 // feed 40 - registerGetRecipes(router, logger, redis); 41 - registerGetRecipe(router, logger, redis); 42 + registerGetRecipes(xrpcRouter, logger, redis); 43 + registerGetRecipe(xrpcRouter, logger, redis); 44 + 45 + const app = new Hono(); 46 + 47 + // mount xrpc router at /xrpc 48 + const xrpcApp = new Hono(); 49 + mountXrpcRouter(xrpcApp, xrpcRouter); 50 + app.route('/xrpc', xrpcApp); 42 51 43 52 const server = Bun.serve({ 44 53 port: process.env.PORT || 3000, 45 - ...router 54 + fetch: app.fetch, 46 55 }); 47 56 48 - console.log(`Server running on http://localhost:${server.port}`); 57 + logger.info({ url: server.url.toString() }, `Recipes.blue API started up`);
+40
apps/api/src/util/hono.ts
··· 1 + import { XRPCRouter } from '@atcute/xrpc-server'; 2 + import type { Context, Hono } from 'hono'; 3 + 4 + export type ApiContext = {}; 5 + 6 + /** 7 + * mounts an @atcute/xrpc-server router into hono as a nested route 8 + * 9 + * basically just bridges the two request handlers since both are 10 + * web standard Request/Response. you can optionally pass hono context 11 + * properties to xrpc handlers via the request object 12 + */ 13 + export const mountXrpcRouter = ( 14 + app: Hono, 15 + router: XRPCRouter, 16 + injectContext?: (c: Context) => ApiContext, 17 + ) => { 18 + app.all('*', async (c) => { 19 + let request = c.req.raw; 20 + 21 + // if context injector provided, attach properties to request 22 + if (injectContext) { 23 + const contextData = injectContext(c); 24 + request = Object.assign(request, contextData); 25 + } 26 + 27 + const response = await router.fetch(request); 28 + return response; 29 + }); 30 + }; 31 + 32 + /** 33 + * helper to extract injected context from xrpc request 34 + * use this in your xrpc handlers to access hono context data 35 + */ 36 + export const getInjectedContext = ( 37 + request: Request 38 + ): ApiContext => { 39 + return request as any as ApiContext; 40 + };
+21 -1
bun.lock
··· 22 22 "@cookware/lexicons": "workspace:*", 23 23 "@libsql/client": "^0.14.0", 24 24 "drizzle-orm": "catalog:", 25 + "hono": "^4.10.7", 25 26 "pino": "^9.5.0", 26 27 }, 27 28 "devDependencies": { ··· 29 30 "@cookware/tsconfig": "workspace:*", 30 31 "@types/bun": "catalog:", 31 32 "drizzle-kit": "^0.29.0", 33 + "pino-pretty": "^13.1.2", 32 34 "rimraf": "^6.0.1", 33 35 }, 34 36 }, ··· 126 128 "dependencies": { 127 129 "@libsql/client": "^0.15.15", 128 130 "drizzle-orm": "catalog:", 131 + "pg": "^8.16.3", 129 132 "zod": "^3.23.8", 130 133 }, 131 134 "devDependencies": { ··· 135 138 "@cookware/tsconfig": "workspace:*", 136 139 "@types/bun": "catalog:", 137 140 "@types/node": "^22.10.1", 141 + "@types/pg": "^8.15.6", 138 142 "drizzle-kit": "^0.29.0", 139 143 "typescript": "^5.2.2", 140 144 }, ··· 817 821 818 822 "@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="], 819 823 820 - "@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="], 824 + "@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="], 821 825 822 826 "@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="], 823 827 ··· 1171 1175 1172 1176 "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], 1173 1177 1178 + "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], 1179 + 1174 1180 "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], 1175 1181 1176 1182 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], ··· 1333 1339 1334 1340 "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 1335 1341 1342 + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], 1343 + 1344 + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], 1345 + 1346 + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], 1347 + 1336 1348 "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], 1337 1349 1350 + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], 1351 + 1338 1352 "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], 1339 1353 1340 1354 "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], 1355 + 1356 + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], 1341 1357 1342 1358 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 1343 1359 ··· 1715 1731 1716 1732 "@opentelemetry/instrumentation-pg/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="], 1717 1733 1734 + "@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="], 1735 + 1718 1736 "@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], 1719 1737 1720 1738 "@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], ··· 1770 1788 "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1771 1789 1772 1790 "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1791 + 1792 + "@types/pg-pool/@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="], 1773 1793 1774 1794 "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 1775 1795
+13
config/dev/db/compose.yaml
··· 1 + --- 2 + volumes: 3 + postgres: {} 4 + 5 + services: 6 + postgres: 7 + image: postgres:18 8 + environment: 9 + POSTGRES_PASSWORD: postgres 10 + ports: 11 + - 5432:5432 12 + volumes: 13 + - postgres:/var/lib/postgresql/18/docker
-13
config/dev/libsql/compose.yaml
··· 1 - --- 2 - volumes: 3 - libsql: {} 4 - 5 - services: 6 - libsql: 7 - image: ghcr.io/tursodatabase/libsql-server:latest 8 - environment: 9 - SQLD_NODE: primary 10 - ports: 11 - - 4001:8080 12 - volumes: 13 - - libsql:/var/lib/sqld
+12
config/dev/redis/compose.yaml
··· 1 + --- 2 + volumes: 3 + redis: 4 + 5 + services: 6 + redis: 7 + image: redis:8 8 + command: redis-server --save 60 1 --loglevel warning 9 + ports: 10 + - 6379:6379 11 + volumes: 12 + - redis:/data
+2 -32
docker-compose.yaml
··· 1 1 --- 2 2 include: 3 - - path: config/dev/caddy/compose.yaml 4 - - path: config/dev/libsql/compose.yaml 3 + - path: config/dev/db/compose.yaml 4 + - path: config/dev/redis/compose.yaml 5 5 6 6 networks: 7 7 recipesblue: 8 - 9 - services: 10 - redis: 11 - image: redis:8 12 - ports: [6379:6379] 13 - 14 - api: 15 - build: 16 - context: . 17 - dockerfile: apps/api/Dockerfile 18 - restart: unless-stopped 19 - networks: [recipesblue] 20 - ports: 21 - - "3000:3000" 22 - environment: 23 - - DATABASE_URL=http://libsql:8080 24 - - PORT=3000 25 - depends_on: 26 - - libsql 27 - 28 - ingester: 29 - build: 30 - context: . 31 - dockerfile: apps/ingester/Dockerfile 32 - restart: unless-stopped 33 - networks: [recipesblue] 34 - environment: 35 - - DATABASE_URL=http://libsql:8080 36 - depends_on: 37 - - libsql
+2 -3
libs/database/drizzle.config.ts
··· 3 3 export default { 4 4 schema: "./lib/schema.ts", 5 5 out: "./migrations", 6 - dialect: "turso", 6 + dialect: "postgresql", 7 7 dbCredentials: { 8 - url: process.env.DATABASE_URL || "http://localhost:4001", 9 - authToken: process.env.DATABASE_AUTH_TOKEN 8 + url: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/postgres', 10 9 } 11 10 } satisfies Config;
+5 -6
libs/database/lib/index.ts
··· 1 - import { drizzle } from 'drizzle-orm/libsql'; 2 - import { createClient } from '@libsql/client'; 1 + import { drizzle } from 'drizzle-orm/node-postgres'; 2 + import { Pool } from 'pg'; 3 3 4 - const client = createClient({ 5 - url: process.env.TURSO_CONNECTION_URL || 'http://localhost:4001', 6 - authToken: process.env.TURSO_AUTH_TOKEN || '', 4 + const pool = new Pool({ 5 + connectionString: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/postgres', 7 6 }); 8 7 9 8 import * as schema from './schema.js'; 10 - export const db = drizzle(client, { schema }); 9 + export const db = drizzle(pool, { schema }); 11 10 12 11 // Re-export drizzle-orm functions to ensure single instance 13 12 export { eq, and, or, desc, asc, sql } from 'drizzle-orm';
+14 -13
libs/database/lib/schema.ts
··· 1 - import { customType, index, int, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; 2 - import { BlueRecipesFeedRecipe, BlueRecipesActorProfile } from "@cookware/lexicons"; 1 + import { customType, index, integer, primaryKey, pgTable, text, jsonb, varchar } from "drizzle-orm/pg-core"; 2 + import { BlueRecipesFeedRecipe } from "@cookware/lexicons"; 3 3 import { Cid, isCid, ResourceUri, type AtprotoDid } from "@atcute/lexicons/syntax"; 4 4 import { Blob, LegacyBlob } from "@atcute/lexicons"; 5 5 import { relations, sql, type SQL } from "drizzle-orm"; ··· 53 53 }, 54 54 }); 55 55 56 - export const profilesTable = sqliteTable("profiles", { 56 + export const profilesTable = pgTable("profiles", { 57 57 uri: text('uri') 58 58 .generatedAlwaysAs((): SQL => sql`'at://' || ${profilesTable.did} || '/blue.recipes.actor.profile/self'`) 59 59 .$type<ResourceUri>(), 60 + 60 61 cid: text("cid").$type<Cid>().notNull(), 61 62 did: text("did").$type<AtprotoDid>().notNull().primaryKey(), 62 63 ingestedAt: dateIsoText("ingested_at").notNull().default(sql`CURRENT_TIMESTAMP`), 63 64 64 - displayName: text('display_name', { length: 640 }).notNull(), 65 - description: text('description', { length: 2500 }), 66 - pronouns: text('pronouns', { length: 200 }), 65 + displayName: varchar('display_name', { length: 640 }).notNull(), 66 + description: varchar('description', { length: 2500 }), 67 + pronouns: varchar('pronouns', { length: 200 }), 67 68 website: text('website'), 68 69 avatarRef: atBlob('avatar'), 69 70 bannerRef: atBlob('banner'), ··· 74 75 index('profiles_iat_idx').on(t.ingestedAt), 75 76 ])); 76 77 77 - export const recipeTable = sqliteTable("recipes", { 78 + export const recipeTable = pgTable("recipes", { 78 79 uri: text('uri') 79 80 .generatedAlwaysAs((): SQL => sql`'at://' || ${recipeTable.did} || '/blue.recipes.feed.recipe/' || ${recipeTable.rkey}`), 80 81 ··· 88 89 imageRef: atBlob('image'), 89 90 90 91 title: text('title').notNull(), 91 - time: int('time').notNull().default(0), 92 - serves: int('serves'), 92 + time: integer('time').notNull().default(0), 93 + serves: integer('serves'), 93 94 description: text('description'), 94 95 95 - ingredients: text('ingredients', { mode: 'json' }).$type<BlueRecipesFeedRecipe.Main['ingredients']>().notNull(), 96 - ingredientsCount: int('ingredients_count').generatedAlwaysAs((): SQL => sql`json_array_length(${recipeTable.ingredients})`), 96 + ingredients: jsonb('ingredients').$type<BlueRecipesFeedRecipe.Main['ingredients']>().notNull(), 97 + ingredientsCount: integer('ingredients_count').generatedAlwaysAs((): SQL => sql`jsonb_array_length(${recipeTable.ingredients})`), 97 98 98 - steps: text('steps', { mode: 'json' }).$type<BlueRecipesFeedRecipe.Main['steps']>().notNull(), 99 - stepsCount: int('steps_count').generatedAlwaysAs((): SQL => sql`json_array_length(${recipeTable.steps})`), 99 + steps: jsonb('steps').$type<BlueRecipesFeedRecipe.Main['steps']>().notNull(), 100 + stepsCount: integer('steps_count').generatedAlwaysAs((): SQL => sql`jsonb_array_length(${recipeTable.steps})`), 100 101 101 102 createdAt: dateIsoText("created_at").notNull(), 102 103 ingestedAt: dateIsoText("ingested_at").notNull().default(sql`CURRENT_TIMESTAMP`),
-16
libs/database/migrations/0000_kind_ultron.sql
··· 1 - CREATE TABLE `recipes` ( 2 - `uri` text GENERATED ALWAYS AS ("author_did" || '/' || "rkey") VIRTUAL, 3 - `author_did` text NOT NULL, 4 - `rkey` text NOT NULL, 5 - `image_ref` text, 6 - `title` text NOT NULL, 7 - `time` integer DEFAULT 0 NOT NULL, 8 - `serves` integer, 9 - `description` text, 10 - `ingredients` text NOT NULL, 11 - `ingredients_count` integer GENERATED ALWAYS AS (json_array_length("ingredients")) VIRTUAL, 12 - `steps` text NOT NULL, 13 - `steps_count` integer GENERATED ALWAYS AS (json_array_length("steps")) VIRTUAL, 14 - `created_at` text NOT NULL, 15 - PRIMARY KEY(`author_did`, `rkey`) 16 - );
+46
libs/database/migrations/0000_young_hellcat.sql
··· 1 + CREATE TABLE IF NOT EXISTS "profiles" ( 2 + "uri" text GENERATED ALWAYS AS ('at://' || "profiles"."did" || '/blue.recipes.actor.profile/self') STORED, 3 + "cid" text NOT NULL, 4 + "did" text PRIMARY KEY NOT NULL, 5 + "ingested_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL, 6 + "display_name" varchar(640) NOT NULL, 7 + "description" varchar(2500), 8 + "pronouns" varchar(200), 9 + "website" text, 10 + "avatar" text, 11 + "banner" text, 12 + "created_at" text NOT NULL 13 + ); 14 + --> statement-breakpoint 15 + CREATE TABLE IF NOT EXISTS "recipes" ( 16 + "uri" text GENERATED ALWAYS AS ('at://' || "recipes"."author_did" || '/blue.recipes.feed.recipe/' || "recipes"."rkey") STORED, 17 + "cid" text NOT NULL, 18 + "author_did" text NOT NULL, 19 + "rkey" text NOT NULL, 20 + "image" text, 21 + "title" text NOT NULL, 22 + "time" integer DEFAULT 0 NOT NULL, 23 + "serves" integer, 24 + "description" text, 25 + "ingredients" jsonb NOT NULL, 26 + "ingredients_count" integer GENERATED ALWAYS AS (jsonb_array_length("recipes"."ingredients")) STORED, 27 + "steps" jsonb NOT NULL, 28 + "steps_count" integer GENERATED ALWAYS AS (jsonb_array_length("recipes"."steps")) STORED, 29 + "created_at" text NOT NULL, 30 + "ingested_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL, 31 + CONSTRAINT "recipes_author_did_rkey_pk" PRIMARY KEY("author_did","rkey") 32 + ); 33 + --> statement-breakpoint 34 + DO $$ BEGIN 35 + ALTER TABLE "recipes" ADD CONSTRAINT "recipes_author_did_profiles_did_fk" FOREIGN KEY ("author_did") REFERENCES "public"."profiles"("did") ON DELETE cascade ON UPDATE no action; 36 + EXCEPTION 37 + WHEN duplicate_object THEN null; 38 + END $$; 39 + --> statement-breakpoint 40 + CREATE INDEX IF NOT EXISTS "profiles_cid_idx" ON "profiles" USING btree ("cid");--> statement-breakpoint 41 + CREATE INDEX IF NOT EXISTS "profiles_cat_idx" ON "profiles" USING btree ("created_at");--> statement-breakpoint 42 + CREATE INDEX IF NOT EXISTS "profiles_iat_idx" ON "profiles" USING btree ("ingested_at");--> statement-breakpoint 43 + CREATE INDEX IF NOT EXISTS "recipes_title_idx" ON "recipes" USING btree ("title");--> statement-breakpoint 44 + CREATE INDEX IF NOT EXISTS "recipes_cid_idx" ON "recipes" USING btree ("cid");--> statement-breakpoint 45 + CREATE INDEX IF NOT EXISTS "recipes_cat_idx" ON "recipes" USING btree ("created_at");--> statement-breakpoint 46 + CREATE INDEX IF NOT EXISTS "recipes_iat_idx" ON "recipes" USING btree ("ingested_at");
-23
libs/database/migrations/0001_past_umar.sql
··· 1 - ALTER TABLE `recipes` RENAME COLUMN "image_ref" TO "image";--> statement-breakpoint 2 - CREATE TABLE `profiles` ( 3 - `uri` text GENERATED ALWAYS AS ('at://' || "did" || '/?/self') VIRTUAL, 4 - `did` text PRIMARY KEY NOT NULL, 5 - `ingested_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, 6 - `display_name` text(640) NOT NULL, 7 - `description` text(2500), 8 - `pronouns` text(200), 9 - `website` text, 10 - `avatar` text, 11 - `banner` text, 12 - `created_at` text NOT NULL 13 - ); 14 - --> statement-breakpoint 15 - CREATE INDEX `profiles_cat_idx` ON `profiles` (`created_at`);--> statement-breakpoint 16 - CREATE INDEX `profiles_iat_idx` ON `profiles` (`ingested_at`);--> statement-breakpoint 17 - ALTER TABLE `recipes` DROP COLUMN `uri`;--> statement-breakpoint 18 - ALTER TABLE `recipes` ADD `uri` text GENERATED ALWAYS AS ('at://' || "author_did" || '/?/' || "rkey") VIRTUAL;--> statement-breakpoint 19 - ALTER TABLE `recipes` ADD `ingested_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL;--> statement-breakpoint 20 - CREATE INDEX `recipes_title_idx` ON `recipes` (`title`);--> statement-breakpoint 21 - CREATE INDEX `recipes_cat_idx` ON `recipes` (`created_at`);--> statement-breakpoint 22 - CREATE INDEX `recipes_iat_idx` ON `recipes` (`ingested_at`);--> statement-breakpoint 23 - ALTER TABLE `recipes` ALTER COLUMN "author_did" TO "author_did" text NOT NULL REFERENCES profiles(did) ON DELETE cascade ON UPDATE no action;
-4
libs/database/migrations/0002_cheerful_venom.sql
··· 1 - ALTER TABLE `profiles` DROP COLUMN `uri`;--> statement-breakpoint 2 - ALTER TABLE `profiles` ADD `uri` text GENERATED ALWAYS AS ('at://' || "did" || '/blue.recipes.actor.profile/self') VIRTUAL;--> statement-breakpoint 3 - ALTER TABLE `recipes` DROP COLUMN `uri`;--> statement-breakpoint 4 - ALTER TABLE `recipes` ADD `uri` text GENERATED ALWAYS AS ('at://' || "author_did" || '/blue.recipes.feed.recipe/' || "rkey") VIRTUAL;
-4
libs/database/migrations/0003_long_blue_marvel.sql
··· 1 - ALTER TABLE `profiles` ADD `cid` text NOT NULL;--> statement-breakpoint 2 - CREATE INDEX `profiles_cid_idx` ON `profiles` (`cid`);--> statement-breakpoint 3 - ALTER TABLE `recipes` ADD `cid` text NOT NULL;--> statement-breakpoint 4 - CREATE INDEX `recipes_cid_idx` ON `recipes` (`cid`);
+255 -46
libs/database/migrations/meta/0000_snapshot.json
··· 1 1 { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "7b2675f9-5d97-4fac-983e-978efd250faf", 2 + "id": "5d896b70-087b-421e-8d94-c100476cd926", 5 3 "prevId": "00000000-0000-0000-0000-000000000000", 4 + "version": "7", 5 + "dialect": "postgresql", 6 6 "tables": { 7 - "recipes": { 7 + "public.profiles": { 8 + "name": "profiles", 9 + "schema": "", 10 + "columns": { 11 + "uri": { 12 + "name": "uri", 13 + "type": "text", 14 + "primaryKey": false, 15 + "notNull": false, 16 + "generated": { 17 + "as": "'at://' || \"profiles\".\"did\" || '/blue.recipes.actor.profile/self'", 18 + "type": "stored" 19 + } 20 + }, 21 + "cid": { 22 + "name": "cid", 23 + "type": "text", 24 + "primaryKey": false, 25 + "notNull": true 26 + }, 27 + "did": { 28 + "name": "did", 29 + "type": "text", 30 + "primaryKey": true, 31 + "notNull": true 32 + }, 33 + "ingested_at": { 34 + "name": "ingested_at", 35 + "type": "text", 36 + "primaryKey": false, 37 + "notNull": true, 38 + "default": "CURRENT_TIMESTAMP" 39 + }, 40 + "display_name": { 41 + "name": "display_name", 42 + "type": "varchar(640)", 43 + "primaryKey": false, 44 + "notNull": true 45 + }, 46 + "description": { 47 + "name": "description", 48 + "type": "varchar(2500)", 49 + "primaryKey": false, 50 + "notNull": false 51 + }, 52 + "pronouns": { 53 + "name": "pronouns", 54 + "type": "varchar(200)", 55 + "primaryKey": false, 56 + "notNull": false 57 + }, 58 + "website": { 59 + "name": "website", 60 + "type": "text", 61 + "primaryKey": false, 62 + "notNull": false 63 + }, 64 + "avatar": { 65 + "name": "avatar", 66 + "type": "text", 67 + "primaryKey": false, 68 + "notNull": false 69 + }, 70 + "banner": { 71 + "name": "banner", 72 + "type": "text", 73 + "primaryKey": false, 74 + "notNull": false 75 + }, 76 + "created_at": { 77 + "name": "created_at", 78 + "type": "text", 79 + "primaryKey": false, 80 + "notNull": true 81 + } 82 + }, 83 + "indexes": { 84 + "profiles_cid_idx": { 85 + "name": "profiles_cid_idx", 86 + "columns": [ 87 + { 88 + "expression": "cid", 89 + "isExpression": false, 90 + "asc": true, 91 + "nulls": "last" 92 + } 93 + ], 94 + "isUnique": false, 95 + "concurrently": false, 96 + "method": "btree", 97 + "with": {} 98 + }, 99 + "profiles_cat_idx": { 100 + "name": "profiles_cat_idx", 101 + "columns": [ 102 + { 103 + "expression": "created_at", 104 + "isExpression": false, 105 + "asc": true, 106 + "nulls": "last" 107 + } 108 + ], 109 + "isUnique": false, 110 + "concurrently": false, 111 + "method": "btree", 112 + "with": {} 113 + }, 114 + "profiles_iat_idx": { 115 + "name": "profiles_iat_idx", 116 + "columns": [ 117 + { 118 + "expression": "ingested_at", 119 + "isExpression": false, 120 + "asc": true, 121 + "nulls": "last" 122 + } 123 + ], 124 + "isUnique": false, 125 + "concurrently": false, 126 + "method": "btree", 127 + "with": {} 128 + } 129 + }, 130 + "foreignKeys": {}, 131 + "compositePrimaryKeys": {}, 132 + "uniqueConstraints": {}, 133 + "policies": {}, 134 + "checkConstraints": {}, 135 + "isRLSEnabled": false 136 + }, 137 + "public.recipes": { 8 138 "name": "recipes", 139 + "schema": "", 9 140 "columns": { 10 141 "uri": { 11 142 "name": "uri", 12 143 "type": "text", 13 144 "primaryKey": false, 14 145 "notNull": false, 15 - "autoincrement": false, 16 146 "generated": { 17 - "as": "(\"author_did\" || '/' || \"rkey\")", 18 - "type": "virtual" 147 + "as": "'at://' || \"recipes\".\"author_did\" || '/blue.recipes.feed.recipe/' || \"recipes\".\"rkey\"", 148 + "type": "stored" 19 149 } 20 150 }, 151 + "cid": { 152 + "name": "cid", 153 + "type": "text", 154 + "primaryKey": false, 155 + "notNull": true 156 + }, 21 157 "author_did": { 22 158 "name": "author_did", 23 159 "type": "text", 24 160 "primaryKey": false, 25 - "notNull": true, 26 - "autoincrement": false 161 + "notNull": true 27 162 }, 28 163 "rkey": { 29 164 "name": "rkey", 30 165 "type": "text", 31 166 "primaryKey": false, 32 - "notNull": true, 33 - "autoincrement": false 167 + "notNull": true 34 168 }, 35 - "image_ref": { 36 - "name": "image_ref", 169 + "image": { 170 + "name": "image", 37 171 "type": "text", 38 172 "primaryKey": false, 39 - "notNull": false, 40 - "autoincrement": false 173 + "notNull": false 41 174 }, 42 175 "title": { 43 176 "name": "title", 44 177 "type": "text", 45 178 "primaryKey": false, 46 - "notNull": true, 47 - "autoincrement": false 179 + "notNull": true 48 180 }, 49 181 "time": { 50 182 "name": "time", 51 183 "type": "integer", 52 184 "primaryKey": false, 53 185 "notNull": true, 54 - "autoincrement": false, 55 186 "default": 0 56 187 }, 57 188 "serves": { 58 189 "name": "serves", 59 190 "type": "integer", 60 191 "primaryKey": false, 61 - "notNull": false, 62 - "autoincrement": false 192 + "notNull": false 63 193 }, 64 194 "description": { 65 195 "name": "description", 66 196 "type": "text", 67 197 "primaryKey": false, 68 - "notNull": false, 69 - "autoincrement": false 198 + "notNull": false 70 199 }, 71 200 "ingredients": { 72 201 "name": "ingredients", 73 - "type": "text", 202 + "type": "jsonb", 74 203 "primaryKey": false, 75 - "notNull": true, 76 - "autoincrement": false 204 + "notNull": true 77 205 }, 78 206 "ingredients_count": { 79 207 "name": "ingredients_count", 80 208 "type": "integer", 81 209 "primaryKey": false, 82 210 "notNull": false, 83 - "autoincrement": false, 84 211 "generated": { 85 - "as": "(json_array_length(\"ingredients\"))", 86 - "type": "virtual" 212 + "as": "jsonb_array_length(\"recipes\".\"ingredients\")", 213 + "type": "stored" 87 214 } 88 215 }, 89 216 "steps": { 90 217 "name": "steps", 91 - "type": "text", 218 + "type": "jsonb", 92 219 "primaryKey": false, 93 - "notNull": true, 94 - "autoincrement": false 220 + "notNull": true 95 221 }, 96 222 "steps_count": { 97 223 "name": "steps_count", 98 224 "type": "integer", 99 225 "primaryKey": false, 100 226 "notNull": false, 101 - "autoincrement": false, 102 227 "generated": { 103 - "as": "(json_array_length(\"steps\"))", 104 - "type": "virtual" 228 + "as": "jsonb_array_length(\"recipes\".\"steps\")", 229 + "type": "stored" 105 230 } 106 231 }, 107 232 "created_at": { 108 233 "name": "created_at", 234 + "type": "text", 235 + "primaryKey": false, 236 + "notNull": true 237 + }, 238 + "ingested_at": { 239 + "name": "ingested_at", 109 240 "type": "text", 110 241 "primaryKey": false, 111 242 "notNull": true, 112 - "autoincrement": false 243 + "default": "CURRENT_TIMESTAMP" 113 244 } 114 245 }, 115 - "indexes": {}, 116 - "foreignKeys": {}, 246 + "indexes": { 247 + "recipes_title_idx": { 248 + "name": "recipes_title_idx", 249 + "columns": [ 250 + { 251 + "expression": "title", 252 + "isExpression": false, 253 + "asc": true, 254 + "nulls": "last" 255 + } 256 + ], 257 + "isUnique": false, 258 + "concurrently": false, 259 + "method": "btree", 260 + "with": {} 261 + }, 262 + "recipes_cid_idx": { 263 + "name": "recipes_cid_idx", 264 + "columns": [ 265 + { 266 + "expression": "cid", 267 + "isExpression": false, 268 + "asc": true, 269 + "nulls": "last" 270 + } 271 + ], 272 + "isUnique": false, 273 + "concurrently": false, 274 + "method": "btree", 275 + "with": {} 276 + }, 277 + "recipes_cat_idx": { 278 + "name": "recipes_cat_idx", 279 + "columns": [ 280 + { 281 + "expression": "created_at", 282 + "isExpression": false, 283 + "asc": true, 284 + "nulls": "last" 285 + } 286 + ], 287 + "isUnique": false, 288 + "concurrently": false, 289 + "method": "btree", 290 + "with": {} 291 + }, 292 + "recipes_iat_idx": { 293 + "name": "recipes_iat_idx", 294 + "columns": [ 295 + { 296 + "expression": "ingested_at", 297 + "isExpression": false, 298 + "asc": true, 299 + "nulls": "last" 300 + } 301 + ], 302 + "isUnique": false, 303 + "concurrently": false, 304 + "method": "btree", 305 + "with": {} 306 + } 307 + }, 308 + "foreignKeys": { 309 + "recipes_author_did_profiles_did_fk": { 310 + "name": "recipes_author_did_profiles_did_fk", 311 + "tableFrom": "recipes", 312 + "tableTo": "profiles", 313 + "columnsFrom": [ 314 + "author_did" 315 + ], 316 + "columnsTo": [ 317 + "did" 318 + ], 319 + "onDelete": "cascade", 320 + "onUpdate": "no action" 321 + } 322 + }, 117 323 "compositePrimaryKeys": { 118 324 "recipes_author_did_rkey_pk": { 325 + "name": "recipes_author_did_rkey_pk", 119 326 "columns": [ 120 327 "author_did", 121 328 "rkey" 122 - ], 123 - "name": "recipes_author_did_rkey_pk" 329 + ] 124 330 } 125 331 }, 126 332 "uniqueConstraints": {}, 127 - "checkConstraints": {} 333 + "policies": {}, 334 + "checkConstraints": {}, 335 + "isRLSEnabled": false 128 336 } 129 337 }, 130 - "views": {}, 131 338 "enums": {}, 339 + "schemas": {}, 340 + "sequences": {}, 341 + "roles": {}, 342 + "policies": {}, 343 + "views": {}, 132 344 "_meta": { 345 + "columns": {}, 133 346 "schemas": {}, 134 - "tables": {}, 135 - "columns": {} 136 - }, 137 - "internal": { 138 - "indexes": {} 347 + "tables": {} 139 348 } 140 349 }
-286
libs/database/migrations/meta/0001_snapshot.json
··· 1 - { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "d6f06b7d-9822-43ee-b96c-3b980a5e4953", 5 - "prevId": "7b2675f9-5d97-4fac-983e-978efd250faf", 6 - "tables": { 7 - "profiles": { 8 - "name": "profiles", 9 - "columns": { 10 - "uri": { 11 - "name": "uri", 12 - "type": "text", 13 - "primaryKey": false, 14 - "notNull": false, 15 - "autoincrement": false, 16 - "generated": { 17 - "as": "('at://' || \"did\" || '/?/self')", 18 - "type": "virtual" 19 - } 20 - }, 21 - "did": { 22 - "name": "did", 23 - "type": "text", 24 - "primaryKey": true, 25 - "notNull": true, 26 - "autoincrement": false 27 - }, 28 - "ingested_at": { 29 - "name": "ingested_at", 30 - "type": "text", 31 - "primaryKey": false, 32 - "notNull": true, 33 - "autoincrement": false, 34 - "default": "CURRENT_TIMESTAMP" 35 - }, 36 - "display_name": { 37 - "name": "display_name", 38 - "type": "text(640)", 39 - "primaryKey": false, 40 - "notNull": true, 41 - "autoincrement": false 42 - }, 43 - "description": { 44 - "name": "description", 45 - "type": "text(2500)", 46 - "primaryKey": false, 47 - "notNull": false, 48 - "autoincrement": false 49 - }, 50 - "pronouns": { 51 - "name": "pronouns", 52 - "type": "text(200)", 53 - "primaryKey": false, 54 - "notNull": false, 55 - "autoincrement": false 56 - }, 57 - "website": { 58 - "name": "website", 59 - "type": "text", 60 - "primaryKey": false, 61 - "notNull": false, 62 - "autoincrement": false 63 - }, 64 - "avatar": { 65 - "name": "avatar", 66 - "type": "text", 67 - "primaryKey": false, 68 - "notNull": false, 69 - "autoincrement": false 70 - }, 71 - "banner": { 72 - "name": "banner", 73 - "type": "text", 74 - "primaryKey": false, 75 - "notNull": false, 76 - "autoincrement": false 77 - }, 78 - "created_at": { 79 - "name": "created_at", 80 - "type": "text", 81 - "primaryKey": false, 82 - "notNull": true, 83 - "autoincrement": false 84 - } 85 - }, 86 - "indexes": { 87 - "profiles_cat_idx": { 88 - "name": "profiles_cat_idx", 89 - "columns": [ 90 - "created_at" 91 - ], 92 - "isUnique": false 93 - }, 94 - "profiles_iat_idx": { 95 - "name": "profiles_iat_idx", 96 - "columns": [ 97 - "ingested_at" 98 - ], 99 - "isUnique": false 100 - } 101 - }, 102 - "foreignKeys": {}, 103 - "compositePrimaryKeys": {}, 104 - "uniqueConstraints": {}, 105 - "checkConstraints": {} 106 - }, 107 - "recipes": { 108 - "name": "recipes", 109 - "columns": { 110 - "uri": { 111 - "name": "uri", 112 - "type": "text", 113 - "primaryKey": false, 114 - "notNull": false, 115 - "autoincrement": false, 116 - "generated": { 117 - "as": "('at://' || \"author_did\" || '/?/' || \"rkey\")", 118 - "type": "virtual" 119 - } 120 - }, 121 - "author_did": { 122 - "name": "author_did", 123 - "type": "text", 124 - "primaryKey": false, 125 - "notNull": true, 126 - "autoincrement": false 127 - }, 128 - "rkey": { 129 - "name": "rkey", 130 - "type": "text", 131 - "primaryKey": false, 132 - "notNull": true, 133 - "autoincrement": false 134 - }, 135 - "image": { 136 - "name": "image", 137 - "type": "text", 138 - "primaryKey": false, 139 - "notNull": false, 140 - "autoincrement": false 141 - }, 142 - "title": { 143 - "name": "title", 144 - "type": "text", 145 - "primaryKey": false, 146 - "notNull": true, 147 - "autoincrement": false 148 - }, 149 - "time": { 150 - "name": "time", 151 - "type": "integer", 152 - "primaryKey": false, 153 - "notNull": true, 154 - "autoincrement": false, 155 - "default": 0 156 - }, 157 - "serves": { 158 - "name": "serves", 159 - "type": "integer", 160 - "primaryKey": false, 161 - "notNull": false, 162 - "autoincrement": false 163 - }, 164 - "description": { 165 - "name": "description", 166 - "type": "text", 167 - "primaryKey": false, 168 - "notNull": false, 169 - "autoincrement": false 170 - }, 171 - "ingredients": { 172 - "name": "ingredients", 173 - "type": "text", 174 - "primaryKey": false, 175 - "notNull": true, 176 - "autoincrement": false 177 - }, 178 - "ingredients_count": { 179 - "name": "ingredients_count", 180 - "type": "integer", 181 - "primaryKey": false, 182 - "notNull": false, 183 - "autoincrement": false, 184 - "generated": { 185 - "as": "(json_array_length(\"ingredients\"))", 186 - "type": "virtual" 187 - } 188 - }, 189 - "steps": { 190 - "name": "steps", 191 - "type": "text", 192 - "primaryKey": false, 193 - "notNull": true, 194 - "autoincrement": false 195 - }, 196 - "steps_count": { 197 - "name": "steps_count", 198 - "type": "integer", 199 - "primaryKey": false, 200 - "notNull": false, 201 - "autoincrement": false, 202 - "generated": { 203 - "as": "(json_array_length(\"steps\"))", 204 - "type": "virtual" 205 - } 206 - }, 207 - "created_at": { 208 - "name": "created_at", 209 - "type": "text", 210 - "primaryKey": false, 211 - "notNull": true, 212 - "autoincrement": false 213 - }, 214 - "ingested_at": { 215 - "name": "ingested_at", 216 - "type": "text", 217 - "primaryKey": false, 218 - "notNull": true, 219 - "autoincrement": false, 220 - "default": "CURRENT_TIMESTAMP" 221 - } 222 - }, 223 - "indexes": { 224 - "recipes_title_idx": { 225 - "name": "recipes_title_idx", 226 - "columns": [ 227 - "title" 228 - ], 229 - "isUnique": false 230 - }, 231 - "recipes_cat_idx": { 232 - "name": "recipes_cat_idx", 233 - "columns": [ 234 - "created_at" 235 - ], 236 - "isUnique": false 237 - }, 238 - "recipes_iat_idx": { 239 - "name": "recipes_iat_idx", 240 - "columns": [ 241 - "ingested_at" 242 - ], 243 - "isUnique": false 244 - } 245 - }, 246 - "foreignKeys": { 247 - "recipes_author_did_profiles_did_fk": { 248 - "name": "recipes_author_did_profiles_did_fk", 249 - "tableFrom": "recipes", 250 - "tableTo": "profiles", 251 - "columnsFrom": [ 252 - "author_did" 253 - ], 254 - "columnsTo": [ 255 - "did" 256 - ], 257 - "onDelete": "cascade", 258 - "onUpdate": "no action" 259 - } 260 - }, 261 - "compositePrimaryKeys": { 262 - "recipes_author_did_rkey_pk": { 263 - "columns": [ 264 - "author_did", 265 - "rkey" 266 - ], 267 - "name": "recipes_author_did_rkey_pk" 268 - } 269 - }, 270 - "uniqueConstraints": {}, 271 - "checkConstraints": {} 272 - } 273 - }, 274 - "views": {}, 275 - "enums": {}, 276 - "_meta": { 277 - "schemas": {}, 278 - "tables": {}, 279 - "columns": { 280 - "\"recipes\".\"image_ref\"": "\"recipes\".\"image\"" 281 - } 282 - }, 283 - "internal": { 284 - "indexes": {} 285 - } 286 - }
-284
libs/database/migrations/meta/0002_snapshot.json
··· 1 - { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "25f6fc02-0357-4a4a-a43c-6fc138a21401", 5 - "prevId": "d6f06b7d-9822-43ee-b96c-3b980a5e4953", 6 - "tables": { 7 - "profiles": { 8 - "name": "profiles", 9 - "columns": { 10 - "uri": { 11 - "name": "uri", 12 - "type": "text", 13 - "primaryKey": false, 14 - "notNull": false, 15 - "autoincrement": false, 16 - "generated": { 17 - "as": "('at://' || \"did\" || '/blue.recipes.actor.profile/self')", 18 - "type": "virtual" 19 - } 20 - }, 21 - "did": { 22 - "name": "did", 23 - "type": "text", 24 - "primaryKey": true, 25 - "notNull": true, 26 - "autoincrement": false 27 - }, 28 - "ingested_at": { 29 - "name": "ingested_at", 30 - "type": "text", 31 - "primaryKey": false, 32 - "notNull": true, 33 - "autoincrement": false, 34 - "default": "CURRENT_TIMESTAMP" 35 - }, 36 - "display_name": { 37 - "name": "display_name", 38 - "type": "text(640)", 39 - "primaryKey": false, 40 - "notNull": true, 41 - "autoincrement": false 42 - }, 43 - "description": { 44 - "name": "description", 45 - "type": "text(2500)", 46 - "primaryKey": false, 47 - "notNull": false, 48 - "autoincrement": false 49 - }, 50 - "pronouns": { 51 - "name": "pronouns", 52 - "type": "text(200)", 53 - "primaryKey": false, 54 - "notNull": false, 55 - "autoincrement": false 56 - }, 57 - "website": { 58 - "name": "website", 59 - "type": "text", 60 - "primaryKey": false, 61 - "notNull": false, 62 - "autoincrement": false 63 - }, 64 - "avatar": { 65 - "name": "avatar", 66 - "type": "text", 67 - "primaryKey": false, 68 - "notNull": false, 69 - "autoincrement": false 70 - }, 71 - "banner": { 72 - "name": "banner", 73 - "type": "text", 74 - "primaryKey": false, 75 - "notNull": false, 76 - "autoincrement": false 77 - }, 78 - "created_at": { 79 - "name": "created_at", 80 - "type": "text", 81 - "primaryKey": false, 82 - "notNull": true, 83 - "autoincrement": false 84 - } 85 - }, 86 - "indexes": { 87 - "profiles_cat_idx": { 88 - "name": "profiles_cat_idx", 89 - "columns": [ 90 - "created_at" 91 - ], 92 - "isUnique": false 93 - }, 94 - "profiles_iat_idx": { 95 - "name": "profiles_iat_idx", 96 - "columns": [ 97 - "ingested_at" 98 - ], 99 - "isUnique": false 100 - } 101 - }, 102 - "foreignKeys": {}, 103 - "compositePrimaryKeys": {}, 104 - "uniqueConstraints": {}, 105 - "checkConstraints": {} 106 - }, 107 - "recipes": { 108 - "name": "recipes", 109 - "columns": { 110 - "uri": { 111 - "name": "uri", 112 - "type": "text", 113 - "primaryKey": false, 114 - "notNull": false, 115 - "autoincrement": false, 116 - "generated": { 117 - "as": "('at://' || \"author_did\" || '/blue.recipes.feed.recipe/' || \"rkey\")", 118 - "type": "virtual" 119 - } 120 - }, 121 - "author_did": { 122 - "name": "author_did", 123 - "type": "text", 124 - "primaryKey": false, 125 - "notNull": true, 126 - "autoincrement": false 127 - }, 128 - "rkey": { 129 - "name": "rkey", 130 - "type": "text", 131 - "primaryKey": false, 132 - "notNull": true, 133 - "autoincrement": false 134 - }, 135 - "image": { 136 - "name": "image", 137 - "type": "text", 138 - "primaryKey": false, 139 - "notNull": false, 140 - "autoincrement": false 141 - }, 142 - "title": { 143 - "name": "title", 144 - "type": "text", 145 - "primaryKey": false, 146 - "notNull": true, 147 - "autoincrement": false 148 - }, 149 - "time": { 150 - "name": "time", 151 - "type": "integer", 152 - "primaryKey": false, 153 - "notNull": true, 154 - "autoincrement": false, 155 - "default": 0 156 - }, 157 - "serves": { 158 - "name": "serves", 159 - "type": "integer", 160 - "primaryKey": false, 161 - "notNull": false, 162 - "autoincrement": false 163 - }, 164 - "description": { 165 - "name": "description", 166 - "type": "text", 167 - "primaryKey": false, 168 - "notNull": false, 169 - "autoincrement": false 170 - }, 171 - "ingredients": { 172 - "name": "ingredients", 173 - "type": "text", 174 - "primaryKey": false, 175 - "notNull": true, 176 - "autoincrement": false 177 - }, 178 - "ingredients_count": { 179 - "name": "ingredients_count", 180 - "type": "integer", 181 - "primaryKey": false, 182 - "notNull": false, 183 - "autoincrement": false, 184 - "generated": { 185 - "as": "(json_array_length(\"ingredients\"))", 186 - "type": "virtual" 187 - } 188 - }, 189 - "steps": { 190 - "name": "steps", 191 - "type": "text", 192 - "primaryKey": false, 193 - "notNull": true, 194 - "autoincrement": false 195 - }, 196 - "steps_count": { 197 - "name": "steps_count", 198 - "type": "integer", 199 - "primaryKey": false, 200 - "notNull": false, 201 - "autoincrement": false, 202 - "generated": { 203 - "as": "(json_array_length(\"steps\"))", 204 - "type": "virtual" 205 - } 206 - }, 207 - "created_at": { 208 - "name": "created_at", 209 - "type": "text", 210 - "primaryKey": false, 211 - "notNull": true, 212 - "autoincrement": false 213 - }, 214 - "ingested_at": { 215 - "name": "ingested_at", 216 - "type": "text", 217 - "primaryKey": false, 218 - "notNull": true, 219 - "autoincrement": false, 220 - "default": "CURRENT_TIMESTAMP" 221 - } 222 - }, 223 - "indexes": { 224 - "recipes_title_idx": { 225 - "name": "recipes_title_idx", 226 - "columns": [ 227 - "title" 228 - ], 229 - "isUnique": false 230 - }, 231 - "recipes_cat_idx": { 232 - "name": "recipes_cat_idx", 233 - "columns": [ 234 - "created_at" 235 - ], 236 - "isUnique": false 237 - }, 238 - "recipes_iat_idx": { 239 - "name": "recipes_iat_idx", 240 - "columns": [ 241 - "ingested_at" 242 - ], 243 - "isUnique": false 244 - } 245 - }, 246 - "foreignKeys": { 247 - "recipes_author_did_profiles_did_fk": { 248 - "name": "recipes_author_did_profiles_did_fk", 249 - "tableFrom": "recipes", 250 - "tableTo": "profiles", 251 - "columnsFrom": [ 252 - "author_did" 253 - ], 254 - "columnsTo": [ 255 - "did" 256 - ], 257 - "onDelete": "cascade", 258 - "onUpdate": "no action" 259 - } 260 - }, 261 - "compositePrimaryKeys": { 262 - "recipes_author_did_rkey_pk": { 263 - "columns": [ 264 - "author_did", 265 - "rkey" 266 - ], 267 - "name": "recipes_author_did_rkey_pk" 268 - } 269 - }, 270 - "uniqueConstraints": {}, 271 - "checkConstraints": {} 272 - } 273 - }, 274 - "views": {}, 275 - "enums": {}, 276 - "_meta": { 277 - "schemas": {}, 278 - "tables": {}, 279 - "columns": {} 280 - }, 281 - "internal": { 282 - "indexes": {} 283 - } 284 - }
-312
libs/database/migrations/meta/0003_snapshot.json
··· 1 - { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "ca3337d9-69a0-468d-8364-0f05e91a0233", 5 - "prevId": "25f6fc02-0357-4a4a-a43c-6fc138a21401", 6 - "tables": { 7 - "profiles": { 8 - "name": "profiles", 9 - "columns": { 10 - "uri": { 11 - "name": "uri", 12 - "type": "text", 13 - "primaryKey": false, 14 - "notNull": false, 15 - "autoincrement": false, 16 - "generated": { 17 - "as": "('at://' || \"did\" || '/blue.recipes.actor.profile/self')", 18 - "type": "virtual" 19 - } 20 - }, 21 - "cid": { 22 - "name": "cid", 23 - "type": "text", 24 - "primaryKey": false, 25 - "notNull": true, 26 - "autoincrement": false 27 - }, 28 - "did": { 29 - "name": "did", 30 - "type": "text", 31 - "primaryKey": true, 32 - "notNull": true, 33 - "autoincrement": false 34 - }, 35 - "ingested_at": { 36 - "name": "ingested_at", 37 - "type": "text", 38 - "primaryKey": false, 39 - "notNull": true, 40 - "autoincrement": false, 41 - "default": "CURRENT_TIMESTAMP" 42 - }, 43 - "display_name": { 44 - "name": "display_name", 45 - "type": "text(640)", 46 - "primaryKey": false, 47 - "notNull": true, 48 - "autoincrement": false 49 - }, 50 - "description": { 51 - "name": "description", 52 - "type": "text(2500)", 53 - "primaryKey": false, 54 - "notNull": false, 55 - "autoincrement": false 56 - }, 57 - "pronouns": { 58 - "name": "pronouns", 59 - "type": "text(200)", 60 - "primaryKey": false, 61 - "notNull": false, 62 - "autoincrement": false 63 - }, 64 - "website": { 65 - "name": "website", 66 - "type": "text", 67 - "primaryKey": false, 68 - "notNull": false, 69 - "autoincrement": false 70 - }, 71 - "avatar": { 72 - "name": "avatar", 73 - "type": "text", 74 - "primaryKey": false, 75 - "notNull": false, 76 - "autoincrement": false 77 - }, 78 - "banner": { 79 - "name": "banner", 80 - "type": "text", 81 - "primaryKey": false, 82 - "notNull": false, 83 - "autoincrement": false 84 - }, 85 - "created_at": { 86 - "name": "created_at", 87 - "type": "text", 88 - "primaryKey": false, 89 - "notNull": true, 90 - "autoincrement": false 91 - } 92 - }, 93 - "indexes": { 94 - "profiles_cid_idx": { 95 - "name": "profiles_cid_idx", 96 - "columns": [ 97 - "cid" 98 - ], 99 - "isUnique": false 100 - }, 101 - "profiles_cat_idx": { 102 - "name": "profiles_cat_idx", 103 - "columns": [ 104 - "created_at" 105 - ], 106 - "isUnique": false 107 - }, 108 - "profiles_iat_idx": { 109 - "name": "profiles_iat_idx", 110 - "columns": [ 111 - "ingested_at" 112 - ], 113 - "isUnique": false 114 - } 115 - }, 116 - "foreignKeys": {}, 117 - "compositePrimaryKeys": {}, 118 - "uniqueConstraints": {}, 119 - "checkConstraints": {} 120 - }, 121 - "recipes": { 122 - "name": "recipes", 123 - "columns": { 124 - "uri": { 125 - "name": "uri", 126 - "type": "text", 127 - "primaryKey": false, 128 - "notNull": false, 129 - "autoincrement": false, 130 - "generated": { 131 - "as": "('at://' || \"author_did\" || '/blue.recipes.feed.recipe/' || \"rkey\")", 132 - "type": "virtual" 133 - } 134 - }, 135 - "cid": { 136 - "name": "cid", 137 - "type": "text", 138 - "primaryKey": false, 139 - "notNull": true, 140 - "autoincrement": false 141 - }, 142 - "author_did": { 143 - "name": "author_did", 144 - "type": "text", 145 - "primaryKey": false, 146 - "notNull": true, 147 - "autoincrement": false 148 - }, 149 - "rkey": { 150 - "name": "rkey", 151 - "type": "text", 152 - "primaryKey": false, 153 - "notNull": true, 154 - "autoincrement": false 155 - }, 156 - "image": { 157 - "name": "image", 158 - "type": "text", 159 - "primaryKey": false, 160 - "notNull": false, 161 - "autoincrement": false 162 - }, 163 - "title": { 164 - "name": "title", 165 - "type": "text", 166 - "primaryKey": false, 167 - "notNull": true, 168 - "autoincrement": false 169 - }, 170 - "time": { 171 - "name": "time", 172 - "type": "integer", 173 - "primaryKey": false, 174 - "notNull": true, 175 - "autoincrement": false, 176 - "default": 0 177 - }, 178 - "serves": { 179 - "name": "serves", 180 - "type": "integer", 181 - "primaryKey": false, 182 - "notNull": false, 183 - "autoincrement": false 184 - }, 185 - "description": { 186 - "name": "description", 187 - "type": "text", 188 - "primaryKey": false, 189 - "notNull": false, 190 - "autoincrement": false 191 - }, 192 - "ingredients": { 193 - "name": "ingredients", 194 - "type": "text", 195 - "primaryKey": false, 196 - "notNull": true, 197 - "autoincrement": false 198 - }, 199 - "ingredients_count": { 200 - "name": "ingredients_count", 201 - "type": "integer", 202 - "primaryKey": false, 203 - "notNull": false, 204 - "autoincrement": false, 205 - "generated": { 206 - "as": "(json_array_length(\"ingredients\"))", 207 - "type": "virtual" 208 - } 209 - }, 210 - "steps": { 211 - "name": "steps", 212 - "type": "text", 213 - "primaryKey": false, 214 - "notNull": true, 215 - "autoincrement": false 216 - }, 217 - "steps_count": { 218 - "name": "steps_count", 219 - "type": "integer", 220 - "primaryKey": false, 221 - "notNull": false, 222 - "autoincrement": false, 223 - "generated": { 224 - "as": "(json_array_length(\"steps\"))", 225 - "type": "virtual" 226 - } 227 - }, 228 - "created_at": { 229 - "name": "created_at", 230 - "type": "text", 231 - "primaryKey": false, 232 - "notNull": true, 233 - "autoincrement": false 234 - }, 235 - "ingested_at": { 236 - "name": "ingested_at", 237 - "type": "text", 238 - "primaryKey": false, 239 - "notNull": true, 240 - "autoincrement": false, 241 - "default": "CURRENT_TIMESTAMP" 242 - } 243 - }, 244 - "indexes": { 245 - "recipes_title_idx": { 246 - "name": "recipes_title_idx", 247 - "columns": [ 248 - "title" 249 - ], 250 - "isUnique": false 251 - }, 252 - "recipes_cid_idx": { 253 - "name": "recipes_cid_idx", 254 - "columns": [ 255 - "cid" 256 - ], 257 - "isUnique": false 258 - }, 259 - "recipes_cat_idx": { 260 - "name": "recipes_cat_idx", 261 - "columns": [ 262 - "created_at" 263 - ], 264 - "isUnique": false 265 - }, 266 - "recipes_iat_idx": { 267 - "name": "recipes_iat_idx", 268 - "columns": [ 269 - "ingested_at" 270 - ], 271 - "isUnique": false 272 - } 273 - }, 274 - "foreignKeys": { 275 - "recipes_author_did_profiles_did_fk": { 276 - "name": "recipes_author_did_profiles_did_fk", 277 - "tableFrom": "recipes", 278 - "tableTo": "profiles", 279 - "columnsFrom": [ 280 - "author_did" 281 - ], 282 - "columnsTo": [ 283 - "did" 284 - ], 285 - "onDelete": "cascade", 286 - "onUpdate": "no action" 287 - } 288 - }, 289 - "compositePrimaryKeys": { 290 - "recipes_author_did_rkey_pk": { 291 - "columns": [ 292 - "author_did", 293 - "rkey" 294 - ], 295 - "name": "recipes_author_did_rkey_pk" 296 - } 297 - }, 298 - "uniqueConstraints": {}, 299 - "checkConstraints": {} 300 - } 301 - }, 302 - "views": {}, 303 - "enums": {}, 304 - "_meta": { 305 - "schemas": {}, 306 - "tables": {}, 307 - "columns": {} 308 - }, 309 - "internal": { 310 - "indexes": {} 311 - } 312 - }
+4 -25
libs/database/migrations/meta/_journal.json
··· 1 1 { 2 2 "version": "7", 3 - "dialect": "sqlite", 3 + "dialect": "postgresql", 4 4 "entries": [ 5 5 { 6 6 "idx": 0, 7 - "version": "6", 8 - "when": 1764024817179, 9 - "tag": "0000_kind_ultron", 10 - "breakpoints": true 11 - }, 12 - { 13 - "idx": 1, 14 - "version": "6", 15 - "when": 1764102063385, 16 - "tag": "0001_past_umar", 17 - "breakpoints": true 18 - }, 19 - { 20 - "idx": 2, 21 - "version": "6", 22 - "when": 1764113357363, 23 - "tag": "0002_cheerful_venom", 24 - "breakpoints": true 25 - }, 26 - { 27 - "idx": 3, 28 - "version": "6", 29 - "when": 1764113735823, 30 - "tag": "0003_long_blue_marvel", 7 + "version": "7", 8 + "when": 1764420650497, 9 + "tag": "0000_young_hellcat", 31 10 "breakpoints": true 32 11 } 33 12 ]
+2
libs/database/package.json
··· 27 27 "@cookware/tsconfig": "workspace:*", 28 28 "@types/bun": "catalog:", 29 29 "@types/node": "^22.10.1", 30 + "@types/pg": "^8.15.6", 30 31 "drizzle-kit": "^0.29.0", 31 32 "typescript": "^5.2.2" 32 33 }, 33 34 "dependencies": { 34 35 "@libsql/client": "^0.15.15", 35 36 "drizzle-orm": "catalog:", 37 + "pg": "^8.16.3", 36 38 "zod": "^3.23.8" 37 39 } 38 40 }