Skip to content

Commit fb6de9c

Browse files
committed
finally... migration | closes #171
1 parent 3adb1d6 commit fb6de9c

File tree

5 files changed

+193
-141
lines changed

5 files changed

+193
-141
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
"db:push:dev": "cross-env NODE_ENV=development bun drizzle-kit push",
2727
"db:push:staging": "cross-env NODE_ENV=staging bun drizzle-kit push",
2828
"db:push:prod": "bun drizzle-kit push",
29-
"db:migrate:staging": "bun src/utils/migratedb.ts --staging",
30-
"db:migrate:prod": "bun src/utils/migratedb.ts",
29+
"db:migrate:staging": "bun src/db/migratedb.ts --staging",
30+
"db:migrate:prod": "bun src/db/migratedb.ts",
31+
"db:staging:reset": "bun src/db/resetStagingDatabase.ts --staging",
3132
"dev": "concurrently --names \"WEB,API,BOT\" --prefix-colors \"blue,green,magenta\" \"cd web && bun dev\" \"cd api && cargo watch -x \\\"run -- --dev\\\"\" \"bun --watch . --dev\"",
3233
"dev:bot": "bun --watch . --dev",
3334
"test:jetstream": "bun --watch src/utils/bluesky/jetstream.ts",

src/db/migratedb.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import path from "path";
2+
3+
import { Database } from "bun:sqlite";
4+
import { drizzle as drizzleSqlite } from "drizzle-orm/bun-sqlite";
5+
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
6+
import { Client } from "pg";
7+
8+
import { config } from "../config";
9+
import { getStreamerName } from "../utils/twitch/getStreamerName";
10+
import checkIfChannelIdIsValid from "../utils/youtube/checkIfChannelIdIsValid";
11+
import { Platform } from "../types/types.d";
12+
import { getTwitchToken } from "../utils/twitch/auth";
13+
14+
import * as sqliteSchema from "./schemaSqlite";
15+
import * as pgSchema from "./schema";
16+
import { addNewChannelToTrack } from "./youtube";
17+
import { addNewStreamerToTrack } from "./twitch";
18+
import { discordAddGuildTrackingUser } from "./discord";
19+
20+
// Get Twitch token
21+
if (!(await getTwitchToken())) {
22+
throw new Error("Error getting Twitch token");
23+
}
24+
25+
// SQLite connection
26+
const sqlite = new Database(path.resolve(process.cwd(), "db-prod.sqlite3"));
27+
const sqliteDb = drizzleSqlite(sqlite, { schema: sqliteSchema });
28+
29+
// Postgres connection
30+
const client = new Client({ connectionString: config.databaseUrl });
31+
32+
await client.connect();
33+
const pgDb = drizzlePostgres(client, { schema: pgSchema });
34+
35+
console.log("📋 Starting migration...", config.databaseUrl);
36+
37+
async function migrate() {
38+
// 1. Bot info
39+
const botInfo = await sqliteDb.select().from(sqliteSchema.sqliteBotInfo);
40+
41+
for (const row of botInfo) {
42+
console.log(
43+
`📋 Migrating bot info: guildsTotal=${row.totalServers ?? 0}, totalMembers=${row.totalMembers ?? 0}`,
44+
);
45+
await pgDb.insert(pgSchema.dbBotInfoTable).values({
46+
guildsTotal: row.totalServers ?? 0,
47+
totalMembers: row.totalMembers ?? 0,
48+
});
49+
}
50+
51+
// 2. Discord guilds
52+
const discordRows = await sqliteDb
53+
.select()
54+
.from(sqliteSchema.sqliteDiscord);
55+
56+
for (const row of discordRows) {
57+
console.log("📋 Migrating discord guild:", row.guildId);
58+
await pgDb
59+
.insert(pgSchema.dbDiscordTable)
60+
.values({ guildId: row.guildId ?? "" })
61+
.onConflictDoNothing();
62+
}
63+
64+
// 3. Twitch
65+
const twitchRows = await sqliteDb.select().from(sqliteSchema.sqliteTwitch);
66+
67+
for (const row of twitchRows) {
68+
const twitchChannelName = await getStreamerName(row.twitchChannelId);
69+
70+
if (!twitchChannelName) {
71+
console.log(
72+
`⚠️ Skipping Twitch channel ID ${row.twitchChannelId} as it no longer exists.`,
73+
);
74+
continue;
75+
}
76+
await addNewStreamerToTrack(
77+
row.twitchChannelId,
78+
Boolean(row.isLive),
79+
twitchChannelName,
80+
);
81+
}
82+
83+
// 4. YouTube
84+
const youtubeRows = await sqliteDb
85+
.select()
86+
.from(sqliteSchema.sqliteYouTube);
87+
88+
for (const row of youtubeRows) {
89+
if (!checkIfChannelIdIsValid(row.youtubeChannelId)) {
90+
console.log(
91+
`⚠️ Skipping YouTube channel ID ${row.youtubeChannelId} as it is not a valid channel ID.`,
92+
);
93+
continue;
94+
}
95+
await addNewChannelToTrack(row.youtubeChannelId);
96+
}
97+
98+
// 5. Guild Subscriptions (after Twitch/YouTube exist!)
99+
for (const row of discordRows) {
100+
if (row.guildPlatform === "twitch") {
101+
await discordAddGuildTrackingUser(
102+
row.guildId,
103+
Platform.Twitch,
104+
row.platformUserId,
105+
row.guildChannelId,
106+
row.guildPingRole,
107+
false,
108+
);
109+
} else if (row.guildPlatform === "youtube") {
110+
await discordAddGuildTrackingUser(
111+
row.guildId,
112+
Platform.YouTube,
113+
row.platformUserId,
114+
row.guildChannelId,
115+
row.guildPingRole,
116+
false,
117+
true,
118+
true,
119+
true,
120+
);
121+
}
122+
}
123+
}
124+
125+
await migrate();
126+
127+
// Sometimes the connection pool doesn't close properly
128+
// so we need to force it to close by... doing this
129+
process.exit(0);

src/db/resetStagingDatabase.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Client } from "pg";
2+
3+
import { config } from "../config";
4+
5+
// Connect to Postgres
6+
const client = new Client({ connectionString: config.databaseUrl });
7+
8+
await client.connect();
9+
10+
console.log(`Truncating all tables in database: ${config.databaseUrl}`);
11+
12+
try {
13+
// 1. Get all table names in the public schema
14+
const { rows: tables } = await client.query(`
15+
SELECT table_name
16+
FROM information_schema.tables
17+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';
18+
`);
19+
20+
if (tables.length === 0) {
21+
console.log("No tables found to truncate");
22+
} else {
23+
// 2. Build comma-separated list of tables
24+
const tableNames = tables.map((t) => `"${t.table_name}"`).join(", ");
25+
26+
// 3. Truncate all tables
27+
await client.query(
28+
`TRUNCATE TABLE ${tableNames} RESTART IDENTITY CASCADE;`,
29+
);
30+
console.log(`Truncated ${tables.length} tables: ${tableNames}`);
31+
}
32+
} catch (err) {
33+
console.error("❌ Error truncating tables:", err);
34+
} finally {
35+
await client.end();
36+
process.exit(0);
37+
}

src/db/schemaSqlite.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
2+
3+
export const sqliteBotInfo = sqliteTable("bot_info", {
4+
totalServers: integer("total_servers").notNull(),
5+
totalMembers: integer("total_members").notNull(),
6+
});
7+
8+
export const sqliteDiscord = sqliteTable("discord", {
9+
guildId: text("guild_id").notNull(),
10+
guildChannelId: text("guild_channel_id").notNull(),
11+
guildPlatform: text("guild_platform").notNull(),
12+
platformUserId: text("platform_user_id").notNull(),
13+
guildPingRole: text("guild_ping_role"),
14+
});
15+
16+
export const sqliteTwitch = sqliteTable("twitch", {
17+
twitchChannelId: text("twitch_channel_id").notNull(),
18+
isLive: integer("is_live").notNull(),
19+
});
20+
21+
export const sqliteYouTube = sqliteTable("youtube", {
22+
youtubeChannelId: text("youtube_channel_id").notNull(),
23+
latestVideoId: text("latest_video_id"),
24+
});

src/utils/migratedb.ts

Lines changed: 0 additions & 139 deletions
This file was deleted.

0 commit comments

Comments
 (0)