Skip to content

Commit b08f54d

Browse files
committed
feat(micro): integrate D1 database for micro posts persistence
Add database schema and migrations for storing micro posts with syndication tracking. Update micro CLI commands to read/write posts from D1 database and track syndication results (platform, post ID, and URL) for each successful post. Configure Drizzle ORM and bind D1 database in Wrangler. Add micro navigation link to site header.
1 parent d6ff00e commit b08f54d

10 files changed

Lines changed: 147 additions & 15 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE `micro_posts` (
2+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3+
`content` text(280) NOT NULL,
4+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
5+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
6+
`syndicated_to` text DEFAULT '[]'
7+
);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"version": "6",
3+
"dialect": "sqlite",
4+
"id": "d9e258b9-9f06-43fb-a176-c0c841c5fe8a",
5+
"prevId": "00000000-0000-0000-0000-000000000000",
6+
"tables": {
7+
"micro_posts": {
8+
"name": "micro_posts",
9+
"columns": {
10+
"id": {
11+
"name": "id",
12+
"type": "integer",
13+
"primaryKey": true,
14+
"notNull": true,
15+
"autoincrement": true
16+
},
17+
"content": {
18+
"name": "content",
19+
"type": "text(280)",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"autoincrement": false
23+
},
24+
"created_at": {
25+
"name": "created_at",
26+
"type": "integer",
27+
"primaryKey": false,
28+
"notNull": true,
29+
"autoincrement": false,
30+
"default": "(unixepoch())"
31+
},
32+
"updated_at": {
33+
"name": "updated_at",
34+
"type": "integer",
35+
"primaryKey": false,
36+
"notNull": true,
37+
"autoincrement": false,
38+
"default": "(unixepoch())"
39+
},
40+
"syndicated_to": {
41+
"name": "syndicated_to",
42+
"type": "text",
43+
"primaryKey": false,
44+
"notNull": false,
45+
"autoincrement": false,
46+
"default": "'[]'"
47+
}
48+
},
49+
"indexes": {},
50+
"foreignKeys": {},
51+
"compositePrimaryKeys": {},
52+
"uniqueConstraints": {},
53+
"checkConstraints": {}
54+
}
55+
},
56+
"views": {},
57+
"enums": {},
58+
"_meta": {
59+
"schemas": {},
60+
"tables": {},
61+
"columns": {}
62+
},
63+
"internal": {
64+
"indexes": {}
65+
}
66+
}

db/migrations/meta/_journal.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "7",
3+
"dialect": "sqlite",
4+
"entries": [
5+
{
6+
"idx": 0,
7+
"version": "6",
8+
"when": 1771712423500,
9+
"tag": "0000_red_mother_askani",
10+
"breakpoints": true
11+
}
12+
]
13+
}

db/schema.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { sql } from "drizzle-orm";
2+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3+
4+
export const microPosts = sqliteTable("micro_posts", {
5+
id: integer("id").primaryKey({ autoIncrement: true }),
6+
content: text("content", { length: 280 }).notNull(),
7+
createdAt: integer("created_at", { mode: "timestamp" })
8+
.notNull()
9+
.default(sql`(unixepoch())`),
10+
updatedAt: integer("updated_at", { mode: "timestamp" })
11+
.notNull()
12+
.default(sql`(unixepoch())`)
13+
.$onUpdate(() => new Date()),
14+
syndicatedTo: text("syndicated_to", { mode: "json" })
15+
.$type<Array<{ platform: string; id: string; url: string }>>()
16+
.default(sql`'[]'`),
17+
});

drizzle.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from "drizzle-kit";
2+
3+
export default defineConfig({
4+
schema: "./db/schema.ts",
5+
out: "./db/migrations",
6+
dialect: "sqlite",
7+
driver: "d1-http",
8+
});

packages/micro/src/browse.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ async function showPostActions(
7575
post: Post,
7676
allPosts: Post[],
7777
) {
78-
const syndicatedPlatforms = (post.syndicatedTo as string[] | null) || [];
78+
const syndicatedData =
79+
(post.syndicatedTo as Array<{ platform: string; id: string; url: string }> | null) || [];
80+
const syndicatedPlatforms = syndicatedData.map((s) => s.platform);
7981
const syndicatedText = syndicatedPlatforms.length > 0 ? syndicatedPlatforms.join(", ") : "None";
8082

8183
p.note(
@@ -115,21 +117,23 @@ async function showPostActions(
115117
break;
116118
}
117119
case "open-bluesky": {
118-
if (!syndicatedPlatforms.includes("bluesky")) {
120+
const blueskyData = syndicatedData.find((s) => s.platform === "bluesky");
121+
if (!blueskyData) {
119122
p.note("This post hasn't been syndicated to Bluesky yet", "Not available");
120123
break;
121124
}
122-
// TODO: Store Bluesky post URL in database
123-
p.note("Bluesky URL tracking not yet implemented", "Coming soon");
125+
console.log(`Opening: ${blueskyData.url}`);
126+
await Bun.spawn(["open", blueskyData.url]);
124127
break;
125128
}
126129
case "open-twitter": {
127-
if (!syndicatedPlatforms.includes("twitter")) {
130+
const twitterData = syndicatedData.find((s) => s.platform === "twitter");
131+
if (!twitterData) {
128132
p.note("This post hasn't been syndicated to Twitter yet", "Not available");
129133
break;
130134
}
131-
// TODO: Store Twitter post URL in database
132-
p.note("Twitter URL tracking not yet implemented", "Coming soon");
135+
console.log(`Opening: ${twitterData.url}`);
136+
await Bun.spawn(["open", twitterData.url]);
133137
break;
134138
}
135139
case "delete": {

packages/micro/src/post.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,29 @@ export async function post(content?: string) {
7070
try {
7171
const syndicationResults = await syndicatePost(postContent, config);
7272

73-
const successfulPlatforms = syndicationResults
74-
.filter((r) => r.success)
75-
.map((r) => r.platform);
73+
const successfulSyndications = syndicationResults
74+
.filter((r) => r.success && r.id && r.url)
75+
.map((r) => ({
76+
platform: r.platform,
77+
id: r.id!,
78+
url: r.url!,
79+
}));
7680

7781
const failedPlatforms = syndicationResults.filter((r) => !r.success);
7882

7983
// Update database with successful syndications
80-
if (successfulPlatforms.length > 0) {
84+
if (successfulSyndications.length > 0) {
8185
await db
8286
.update(microPosts)
83-
.set({ syndicatedTo: successfulPlatforms })
87+
.set({ syndicatedTo: successfulSyndications })
8488
.where(eq(microPosts.id, post.id));
8589
}
8690

8791
// Report results
88-
if (successfulPlatforms.length > 0) {
89-
spinner.stop(`Syndicated to: ${successfulPlatforms.join(", ")}`);
92+
if (successfulSyndications.length > 0) {
93+
spinner.stop(
94+
`Syndicated to: ${successfulSyndications.map((s) => s.platform).join(", ")}`,
95+
);
9096
} else {
9197
spinner.stop("Syndication failed");
9298
}

packages/micro/src/syndicate.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface SyndicationResult {
55
platform: string;
66
success: boolean;
77
error?: string;
8+
id?: string;
89
url?: string;
910
}
1011

@@ -75,10 +76,13 @@ async function syndicateToBluesky(
7576
createdAt: new Date().toISOString(),
7677
});
7778

79+
const postId = response.uri.split("/").pop() || "";
80+
7881
return {
7982
platform: "bluesky",
8083
success: true,
81-
url: `https://bsky.app/profile/${credentials.identifier}/post/${response.uri.split("/").pop()}`,
84+
id: postId,
85+
url: `https://bsky.app/profile/${credentials.identifier}/post/${postId}`,
8286
};
8387
} catch (error) {
8488
const errorMessage = error instanceof Error ? error.message : "Unknown error";
@@ -141,6 +145,7 @@ async function syndicateToTwitter(
141145
return {
142146
platform: "twitter",
143147
success: true,
148+
id: tweet.data.id,
144149
url: `https://twitter.com/i/web/status/${tweet.data.id}`,
145150
};
146151
} catch (error) {

src/components/Nav.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface Tab {
1010
const tabs: Tab[] = [
1111
{ href: "/", keyChar: "h", label: "home" },
1212
{ href: "/blog", keyChar: "b", label: "blog" },
13+
{ href: "/micro", keyChar: "m", label: "micro" },
1314
{ href: "/projects", keyChar: "p", label: "projects" },
1415
{ href: "/research", keyChar: "r", label: "research" },
1516
{ href: "/talks", keyChar: "t", label: "talks" },

wrangler.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ enabled = true
99
[assets]
1010
directory = "./dist"
1111
binding = "ASSETS"
12+
13+
[[d1_databases]]
14+
binding = "DB"
15+
database_name = "micro-blog"
16+
database_id = "cc5c17ec-ad2f-41b5-8d5c-cf542604c43b"

0 commit comments

Comments
 (0)