Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
174 changes: 174 additions & 0 deletions scripts/seed-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { argv } from "bun"
import { sql } from "drizzle-orm"
import { DB, SCHEMA } from "@/db"
import { tgMessagesCipher } from "@/routers/tg/messages"
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 = <T>(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 = <T>(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`)
}
Comment thread
viganogabriele marked this conversation as resolved.

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,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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<number, number>()
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: tgMessagesCipher.encrypt(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)
1 change: 0 additions & 1 deletion scripts/seed-user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import "@/server"
import type { User } from "better-auth"
import { argv } from "bun"
import { eq } from "drizzle-orm"
Expand Down
8 changes: 4 additions & 4 deletions src/routers/tg/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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,
Expand Down