From ce5e4db34f7d98fe6655a630954e41072c9234e1 Mon Sep 17 00:00:00 2001 From: deptyped Date: Sat, 25 May 2024 02:55:15 +0300 Subject: [PATCH] Add Prisma example --- .env.example | 3 +- .vscode/extensions.json | 1 + .vscode/settings.json | 4 ++ README.md | 28 ++++++++- .../20230809070833_initial/migration.sql | 10 ++++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 17 ++++++ src/bot/context.ts | 6 ++ src/bot/index.ts | 4 ++ src/main.ts | 9 +++ src/prisma/index.ts | 59 +++++++++++++++++++ 11 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20230809070833_initial/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 src/prisma/index.ts diff --git a/.env.example b/.env.example index 26693c5d..6d1276d0 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,5 @@ BOT_WEBHOOK=https://www.example.com/webhook BOT_WEBHOOK_SECRET=RANDOM_SECRET_VALUE SERVER_HOST=localhost SERVER_PORT=3000 -BOT_ADMINS=[1] \ No newline at end of file +BOT_ADMINS=[1] +DATABASE_URL=file:./dev.db diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e163938c..5886bdea 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "dbaeumer.vscode-eslint", + "prisma.prisma", "macabeus.vscode-fluent", "mikestead.dotenv" ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 15e357d5..6f9f98d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,9 @@ "[javascript]": { "editor.formatOnSave": true, "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "[prisma]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "Prisma.prisma" } } diff --git a/README.md b/README.md index 06e9df56..65187da0 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ Follow these steps to set up and run your bot using this template: ```bash npm install ``` + Run migrations: + ```bash + npx prisma migrate dev + ``` Start the bot in watch mode (auto-reload when code changes): ```bash npm run dev @@ -61,7 +65,20 @@ Follow these steps to set up and run your bot using this template: npm install --only=prod ``` - Set `DEBUG` environment variable to `false` in your `.env` file. + Set `DEBUG` environment variable to `false` in your `.env` file.
+ Update `DATABASE_URL` with a production database. + + ```dotenv + NODE_ENV=production + BOT_WEBHOOK=/webhook + BOT_WEBHOOK_SECRET= + DATABASE_URL= + ``` + + Run migrations: + ```bash + npx prisma migrate deploy + ``` Start the bot in production mode: ```bash @@ -282,6 +299,15 @@ bun add -d @types/bun Specifies method to receive incoming updates (polling or webhook). + + DATABASE_URL + + String + + + Database connection. + + LOG_LEVEL diff --git a/prisma/migrations/20230809070833_initial/migration.sql b/prisma/migrations/20230809070833_initial/migration.sql new file mode 100644 index 00000000..0948999a --- /dev/null +++ b/prisma/migrations/20230809070833_initial/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE "users" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "telegram_id" BIGINT NOT NULL, + "updated_at" DATETIME NOT NULL, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_telegram_id_key" ON "users"("telegram_id"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..e5e5c470 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..4376a914 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,17 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model User { + id Int @id @default(autoincrement()) + telegramId BigInt @unique @map("telegram_id") + updatedAt DateTime @updatedAt @map("updated_at") + createdAt DateTime @default(now()) @map("created_at") + + @@map(name: "users") +} \ No newline at end of file diff --git a/src/bot/context.ts b/src/bot/context.ts index bd97ed17..6342848b 100644 --- a/src/bot/context.ts +++ b/src/bot/context.ts @@ -6,6 +6,7 @@ import type { I18nFlavor } from '@grammyjs/i18n' import type { ParseModeFlavor } from '@grammyjs/parse-mode' import type { Logger } from '#root/logger.js' import type { Config } from '#root/config.js' +import type { PrismaClientX } from '#root/prisma/index.js' export interface SessionData { // field?: string; @@ -14,6 +15,7 @@ export interface SessionData { interface ExtendedContextFlavor { logger: Logger config: Config + prisma: PrismaClientX } export type Context = ParseModeFlavor< @@ -29,15 +31,18 @@ export type Context = ParseModeFlavor< interface Dependencies { logger: Logger config: Config + prisma: PrismaClientX } export function createContextConstructor( { logger, config, + prisma, }: Dependencies, ) { return class extends DefaultContext implements ExtendedContextFlavor { + prisma: PrismaClientX logger: Logger config: Config @@ -48,6 +53,7 @@ export function createContextConstructor( update_id: this.update.update_id, }) this.config = config + this.prisma = prisma } } as unknown as new (update: Update, api: Api, me: UserFromGetMe) => Context } diff --git a/src/bot/index.ts b/src/bot/index.ts index facc7f2b..897d6867 100644 --- a/src/bot/index.ts +++ b/src/bot/index.ts @@ -16,10 +16,12 @@ import { createContextConstructor } from '#root/bot/context.js' import { i18n, isMultipleLocales } from '#root/bot/i18n.js' import type { Logger } from '#root/logger.js' import type { Config } from '#root/config.js' +import type { PrismaClientX } from '#root/prisma/index.js' interface Dependencies { config: Config logger: Logger + prisma: PrismaClientX } interface Options { @@ -35,6 +37,7 @@ export function createBot(token: string, dependencies: Dependencies, options: Op const { config, logger, + prisma, } = dependencies const bot = new TelegramBot(token, { @@ -42,6 +45,7 @@ export function createBot(token: string, dependencies: Dependencies, options: Op ContextConstructor: createContextConstructor({ logger, config, + prisma, }), }) const protectedBot = bot.errorBoundary(errorHandler) diff --git a/src/main.ts b/src/main.ts index 69bb959a..f0aaad7f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import process from 'node:process' import { type RunnerHandle, run } from '@grammyjs/runner' import { logger } from '#root/logger.js' +import { prisma } from '#root/prisma/index.js' import { createBot } from '#root/bot/index.js' import type { PollingConfig, WebhookConfig } from '#root/config.js' import { config } from '#root/config.js' @@ -12,6 +13,7 @@ async function startPolling(config: PollingConfig) { const bot = createBot(config.botToken, { config, logger, + prisma, }) let runner: undefined | RunnerHandle @@ -26,6 +28,9 @@ async function startPolling(config: PollingConfig) { bot.api.deleteWebhook(), ]) + // connect to database + await prisma.$connect() + // start bot runner = run(bot, { runner: { @@ -45,6 +50,7 @@ async function startWebhook(config: WebhookConfig) { const bot = createBot(config.botToken, { config, logger, + prisma, }) const server = createServer({ bot, @@ -62,6 +68,9 @@ async function startWebhook(config: WebhookConfig) { await serverManager.stop() }) + // connect to database + await prisma.$connect() + // to prevent receiving updates before the bot is ready await bot.init() diff --git a/src/prisma/index.ts b/src/prisma/index.ts new file mode 100644 index 00000000..c7d11219 --- /dev/null +++ b/src/prisma/index.ts @@ -0,0 +1,59 @@ +import type { Prisma } from '@prisma/client' +import { PrismaClient } from '@prisma/client' +import { logger } from '#root/logger.js' + +export const prisma = new PrismaClient({ + log: [ + { + emit: 'event', + level: 'query', + }, + { + emit: 'event', + level: 'error', + }, + { + emit: 'event', + level: 'info', + }, + { + emit: 'event', + level: 'warn', + }, + ], +}) + +export type PrismaClientX = typeof prisma + +prisma.$on('query', (event: Prisma.QueryEvent) => { + logger.debug({ + msg: 'database query', + query: event.query, + params: event.params, + duration: event.duration, + }) +}) + +prisma.$on('error', (event: Prisma.LogEvent) => { + logger.error({ + msg: 'database error', + target: event.target, + message: event.message, + }) +}) + +prisma.$on('info', (event: Prisma.LogEvent) => { + logger.info({ + msg: 'database info', + target: event.target, + message: event.message, + }) +}) + +prisma.$on('warn', (event: Prisma.LogEvent) => { + logger.warn({ + msg: 'database warning', + target: event.target, + message: event.message, + }) +})