From ce074385305bd7eca4abad4631b0c42e91674c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Vigan=C3=B2?= Date: Wed, 22 Jul 2026 15:43:01 +0200 Subject: [PATCH 1/5] chore: add seed db script --- package.json | 3 +- scripts/seed-db.ts | 129 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 scripts/seed-db.ts diff --git a/package.json b/package.json index cbca416..d92d20c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "typecheck": "tsc --noEmit", "check": "biome check", "check:fix": "biome check --write --unsafe", - "seed:user": "bun ./scripts/seed-user.ts" + "seed:user": "bun ./scripts/seed-user.ts", + "seed:db": "bun ./scripts/seed-db.ts" }, "devDependencies": { "@biomejs/biome": "^2.3.13", diff --git a/scripts/seed-db.ts b/scripts/seed-db.ts new file mode 100644 index 0000000..266ec22 --- /dev/null +++ b/scripts/seed-db.ts @@ -0,0 +1,129 @@ +import "@/server" +import { argv } from "bun" +import { sql } from "drizzle-orm" +import { DB, SCHEMA } from "@/db" +import { encryptUser } from "@/utils/users" + +const USERS_COUNT = 2000 +const GROUPS_COUNT = 1000 +const MESSAGES_PER_USER = 30 +const CHUNK_SIZE = 1000 + +const FIRST_NAMES = [ + "Marco", "Luca", "Giulia", "Sara", "Andrea", "Francesca", "Alessandro", "Chiara", "Matteo", "Elena", + "Davide", "Sofia", "Simone", "Martina", "Riccardo", "Giorgia", "Federico", "Valentina", "Lorenzo", "Beatrice", +] +const LAST_NAMES = [ + "Rossi", "Bianchi", "Ferrari", "Russo", "Colombo", "Ricci", "Marino", "Greco", "Bruno", "Gallo", + "Conti", "De Luca", "Costa", "Giordano", "Mancini", "Rizzo", "Lombardi", "Moretti", "Barbieri", "Fontana", +] +const LANG_CODES = ["it", "en", "es", "fr", "de"] +const GROUP_TOPICS = [ + "Ingegneria", "Informatica", "Matematica", "Fisica", "Design", "Architettura", "Chimica", "Economia", "Biologia", "Medicina", +] +const GROUP_TAGS = ["ing", "info", "mat", "fis", "design"] +const MESSAGE_TEMPLATES = [ + "Ciao a tutti!", + "Qualcuno ha gli appunti della lezione di oggi?", + "Grazie mille per l'aiuto", + "A che ora รจ l'esame?", + "Ottimo lavoro ragazzi", + "Non ho capito questo esercizio, mi aiutate?", + "Perfetto, ci vediamo domani", + "Qualcuno sa dove trovare il materiale del corso?", + "Buona fortuna a tutti per l'esame", + "Ci sono aggiornamenti sull'orario delle lezioni?", +] + +const randomInt = (min: number, max: number) => min + Math.floor(Math.random() * (max - min + 1)) +const randomItem = (arr: T[]): T => arr[randomInt(0, arr.length - 1)] as T +const daysAgo = (maxDays: number) => new Date(Date.now() - randomInt(0, maxDays * 24 * 60 * 60 * 1000)) + +const chunk = (items: T[], size: number): T[][] => { + const chunks: T[][] = [] + for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size)) + return chunks +} + +const force = argv.includes("--force") + +const existing = await Promise.all([ + DB.select().from(SCHEMA.TG.users).limit(1), + DB.select().from(SCHEMA.TG.groups).limit(1), + DB.select().from(SCHEMA.TG.messages).limit(1), +]) +if (existing.some((rows) => rows.length > 0)) { + if (!force) { + console.error("SEED: tg_users, tg_groups or tg_messages already contain data, use --force to wipe and reseed") + process.exit(1) + } + console.warn("SEED: wiping tg_messages, tg_groups, tg_users for reseeding (--force)") + await DB.execute(sql`TRUNCATE TABLE tg_messages, tg_groups, tg_users`) +} + +console.log(`SEED: generating ${USERS_COUNT} users`) +const users = await Promise.all( + Array.from({ length: USERS_COUNT }, async (_, i) => { + const userId = 100_000_000 + i + 1 + const user = await encryptUser({ + id: userId, + firstName: randomItem(FIRST_NAMES), + lastName: Math.random() < 0.9 ? randomItem(LAST_NAMES) : undefined, + username: Math.random() < 0.7 ? `user_${userId}_${Math.random().toString(36).slice(2, 6)}` : undefined, + isBot: Math.random() < 0.02, + langCode: randomItem(LANG_CODES), + }) + return { ...user, createdAt: daysAgo(365) } + }) +) +for (const batch of chunk(users, CHUNK_SIZE)) { + await DB.insert(SCHEMA.TG.users).values(batch) +} + +console.log(`SEED: generating ${GROUPS_COUNT} groups`) +const groups = Array.from({ length: GROUPS_COUNT }, (_, i) => { + const telegramId = -1_000_000_000_000 - i - 1 + return { + telegramId, + title: `Gruppo ${randomItem(GROUP_TOPICS)} ${i + 1}`, + tag: Math.random() < 0.5 ? `#${randomItem(GROUP_TAGS)}` : null, + link: Math.random() < 0.6 ? `https://t.me/joinchat/${Math.random().toString(36).slice(2, 18)}` : null, + hide: Math.random() < 0.05, + createdAt: daysAgo(365), + } +}) +for (const batch of chunk(groups, CHUNK_SIZE)) { + await DB.insert(SCHEMA.TG.groups).values(batch) +} + +console.log(`SEED: generating ${USERS_COUNT * MESSAGES_PER_USER} messages (${MESSAGES_PER_USER} per user)`) +const chatMessageCounters = new Map() +const nextMessageId = (chatId: number) => { + const next = (chatMessageCounters.get(chatId) ?? 0) + 1 + chatMessageCounters.set(chatId, next) + return next +} + +const messages = users.flatMap((user) => + Array.from({ length: MESSAGES_PER_USER }, () => { + const chatId = randomItem(groups).telegramId + return { + chatId, + messageId: nextMessageId(chatId), + authorId: user.userId, + timestamp: daysAgo(180), + message: randomItem(MESSAGE_TEMPLATES), + } + }) +) +for (const batch of chunk(messages, CHUNK_SIZE)) { + await DB.insert(SCHEMA.TG.messages).values(batch) +} + +console.log("SEED: done", { + users: USERS_COUNT, + groups: GROUPS_COUNT, + messages: messages.length, +}) + +process.exit(0) From ae4f30c16ec93f51795bd5e0e869d0a6919f7f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Vigan=C3=B2?= Date: Wed, 22 Jul 2026 15:46:06 +0200 Subject: [PATCH 2/5] chore: biome --- scripts/seed-db.ts | 55 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/scripts/seed-db.ts b/scripts/seed-db.ts index 266ec22..a9a632d 100644 --- a/scripts/seed-db.ts +++ b/scripts/seed-db.ts @@ -10,16 +10,61 @@ const MESSAGES_PER_USER = 30 const CHUNK_SIZE = 1000 const FIRST_NAMES = [ - "Marco", "Luca", "Giulia", "Sara", "Andrea", "Francesca", "Alessandro", "Chiara", "Matteo", "Elena", - "Davide", "Sofia", "Simone", "Martina", "Riccardo", "Giorgia", "Federico", "Valentina", "Lorenzo", "Beatrice", + "Marco", + "Luca", + "Giulia", + "Sara", + "Andrea", + "Francesca", + "Alessandro", + "Chiara", + "Matteo", + "Elena", + "Davide", + "Sofia", + "Simone", + "Martina", + "Riccardo", + "Giorgia", + "Federico", + "Valentina", + "Lorenzo", + "Beatrice", ] const LAST_NAMES = [ - "Rossi", "Bianchi", "Ferrari", "Russo", "Colombo", "Ricci", "Marino", "Greco", "Bruno", "Gallo", - "Conti", "De Luca", "Costa", "Giordano", "Mancini", "Rizzo", "Lombardi", "Moretti", "Barbieri", "Fontana", + "Rossi", + "Bianchi", + "Ferrari", + "Russo", + "Colombo", + "Ricci", + "Marino", + "Greco", + "Bruno", + "Gallo", + "Conti", + "De Luca", + "Costa", + "Giordano", + "Mancini", + "Rizzo", + "Lombardi", + "Moretti", + "Barbieri", + "Fontana", ] const LANG_CODES = ["it", "en", "es", "fr", "de"] const GROUP_TOPICS = [ - "Ingegneria", "Informatica", "Matematica", "Fisica", "Design", "Architettura", "Chimica", "Economia", "Biologia", "Medicina", + "Ingegneria", + "Informatica", + "Matematica", + "Fisica", + "Design", + "Architettura", + "Chimica", + "Economia", + "Biologia", + "Medicina", ] const GROUP_TAGS = ["ing", "info", "mat", "fis", "design"] const MESSAGE_TEMPLATES = [ From eba83912f36fc29c3ab164ce1a1766de2d224cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Vigan=C3=B2?= Date: Wed, 22 Jul 2026 15:51:35 +0200 Subject: [PATCH 3/5] fix: msg encryption --- scripts/seed-db.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/seed-db.ts b/scripts/seed-db.ts index a9a632d..e90dda6 100644 --- a/scripts/seed-db.ts +++ b/scripts/seed-db.ts @@ -2,6 +2,7 @@ import "@/server" import { argv } from "bun" import { sql } from "drizzle-orm" import { DB, SCHEMA } from "@/db" +import { Cipher } from "@/utils/cipher" import { encryptUser } from "@/utils/users" const USERS_COUNT = 2000 @@ -91,6 +92,7 @@ const chunk = (items: T[], size: number): T[][] => { } const force = argv.includes("--force") +const messageCipher = new Cipher("tg.messages") const existing = await Promise.all([ DB.select().from(SCHEMA.TG.users).limit(1), @@ -157,7 +159,7 @@ const messages = users.flatMap((user) => messageId: nextMessageId(chatId), authorId: user.userId, timestamp: daysAgo(180), - message: randomItem(MESSAGE_TEMPLATES), + message: messageCipher.encrypt(randomItem(MESSAGE_TEMPLATES)), } }) ) From c30a6820a1ce2fcec97cd612cd1f16dae33a46e5 Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Wed, 22 Jul 2026 16:05:24 +0200 Subject: [PATCH 4/5] fix: double cipher creation --- scripts/seed-db.ts | 5 ++--- src/routers/tg/messages.ts | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/seed-db.ts b/scripts/seed-db.ts index e90dda6..cc84efe 100644 --- a/scripts/seed-db.ts +++ b/scripts/seed-db.ts @@ -2,7 +2,7 @@ import "@/server" import { argv } from "bun" import { sql } from "drizzle-orm" import { DB, SCHEMA } from "@/db" -import { Cipher } from "@/utils/cipher" +import { tgMessagesCipher } from "@/routers/tg/messages" import { encryptUser } from "@/utils/users" const USERS_COUNT = 2000 @@ -92,7 +92,6 @@ const chunk = (items: T[], size: number): T[][] => { } const force = argv.includes("--force") -const messageCipher = new Cipher("tg.messages") const existing = await Promise.all([ DB.select().from(SCHEMA.TG.users).limit(1), @@ -159,7 +158,7 @@ const messages = users.flatMap((user) => messageId: nextMessageId(chatId), authorId: user.userId, timestamp: daysAgo(180), - message: messageCipher.encrypt(randomItem(MESSAGE_TEMPLATES)), + message: tgMessagesCipher.encrypt(randomItem(MESSAGE_TEMPLATES)), } }) ) diff --git a/src/routers/tg/messages.ts b/src/routers/tg/messages.ts index 74deda3..45b87a9 100644 --- a/src/routers/tg/messages.ts +++ b/src/routers/tg/messages.ts @@ -5,7 +5,7 @@ import { logger } from "@/logger" import { createTRPCRouter, publicProcedure } from "@/trpc" import { Cipher, DecryptError } from "@/utils/cipher" -const cipher = new Cipher("tg.messages") +export const tgMessagesCipher = new Cipher("tg.messages") const s = SCHEMA.TG const message = z.object({ @@ -56,7 +56,7 @@ export default createTRPCRouter({ if (!res) return { message: null, error: "NOT_FOUND" } try { - const encryptedMessage = cipher.decrypt(res.message) + const encryptedMessage = tgMessagesCipher.decrypt(res.message) const message: Message = { message: encryptedMessage, timestamp: res.timestamp, @@ -83,7 +83,7 @@ export default createTRPCRouter({ try { const messages = input.messages.map(async (m) => ({ ...m, - message: cipher.encrypt(m.message), + message: tgMessagesCipher.encrypt(m.message), })) const awaitedMessages = await Promise.all(messages) await DB.insert(s.messages).values(awaitedMessages).onConflictDoNothing() @@ -126,7 +126,7 @@ export default createTRPCRouter({ try { const messages = res.map(({ messages: e, groups: group }) => { - const decryptedMessage = cipher.decrypt(e.message) + const decryptedMessage = tgMessagesCipher.decrypt(e.message) const message: Message = { message: decryptedMessage, timestamp: e.timestamp, From 80b301c985b93b755aabafaae66547577d04cc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Vigan=C3=B2?= Date: Wed, 22 Jul 2026 22:51:48 +0200 Subject: [PATCH 5/5] Remove server bootstrap from seed scripts - Drop `@/server` import from `seed-db` and `seed-user` - Keep seeding scripts lightweight and self-contained --- scripts/seed-db.ts | 1 - scripts/seed-user.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/scripts/seed-db.ts b/scripts/seed-db.ts index cc84efe..16e5843 100644 --- a/scripts/seed-db.ts +++ b/scripts/seed-db.ts @@ -1,4 +1,3 @@ -import "@/server" import { argv } from "bun" import { sql } from "drizzle-orm" import { DB, SCHEMA } from "@/db" diff --git a/scripts/seed-user.ts b/scripts/seed-user.ts index f9169a8..16f560a 100644 --- a/scripts/seed-user.ts +++ b/scripts/seed-user.ts @@ -1,4 +1,3 @@ -import "@/server" import type { User } from "better-auth" import { argv } from "bun" import { eq } from "drizzle-orm"