+1
-2
.env.example
+1
-2
.env.example
+3
-1
apps/api/package.json
+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
+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
+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
+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
+13
config/dev/db/compose.yaml
-13
config/dev/libsql/compose.yaml
-13
config/dev/libsql/compose.yaml
+12
config/dev/redis/compose.yaml
+12
config/dev/redis/compose.yaml
+2
-32
docker-compose.yaml
+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
+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
+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
+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
-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
+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
-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
-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
-4
libs/database/migrations/0003_long_blue_marvel.sql
+255
-46
libs/database/migrations/meta/0000_snapshot.json
+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
-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
-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
-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
+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
+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
}