diff --git a/Dockerfile b/Dockerfile index 6f4ac15..fbfe8ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,15 @@ -FROM denoland/deno:2.0.2 +FROM denoland/deno:1.46.3 WORKDIR /app COPY . . -RUN deno cache ./main.ts --allow-import +RUN deno cache ./main.ts +# RUN deno cache ./main.ts --allow-import RUN mkdir bin # Not working # RUN deno compile ./main.ts -o ./bin/mic --allow-all --allow-import # ENTRYPOINT [ "./bin/mic" ] -ENTRYPOINT [ "deno", "run", "--allow-all", "main.ts" ] \ No newline at end of file +ENTRYPOINT [ "deno", "run", "--allow-all", "main.ts" ] diff --git a/bot/bot.ts b/bot/bot.ts index a06af11..ab68e5d 100644 --- a/bot/bot.ts +++ b/bot/bot.ts @@ -13,7 +13,6 @@ class BotUnknownOnStartErr extends Err { } export const runBot = async (cfg: config.BotConfig, initMode: (bot: Bot) => void) => { - // Bot initialization & setup const bot = new Bot(cfg.bot_token) bot.use(session({ initial: defaultSessionData })) bot.catch((err: BotError) => { @@ -22,8 +21,9 @@ export const runBot = async (cfg: config.BotConfig, initMode: (bot: Bot) => }) initMode(bot) + await bot.init() - console.log("Starting bot") + console.log(`starting bot ${bot.botInfo.username}`) await bot.start().catch(err => { throw new BotUnknownOnStartErr("Unknown error while starting the bot", { cause: err }) diff --git a/bot/normal_mode/captcha.ts b/bot/normal_mode/captcha.ts index afe0283..249e7ac 100644 --- a/bot/normal_mode/captcha.ts +++ b/bot/normal_mode/captcha.ts @@ -1,8 +1,8 @@ import { Filter } from "https://deno.land/x/grammy@v1.30.0/mod.ts"; -import { BotConfig } from "../../cfg/bot.ts" +import { BotConfig } from "../../cfg/config.ts"; import { Ctx } from "../ctx.ts"; import { LangManager } from "../lang/export.ts"; -import { Kysely } from "npm:kysely"; +import { Kysely } from 'npm:kysely'; import { Database } from "../../repo/exports.ts"; import { checkUserRestrictions, getActiveInviteLink } from "../../core/users.ts"; import { CheckUserOut } from "./user_managment.ts"; @@ -128,6 +128,7 @@ const checkCaptchaSolution = async (ctx: Filter, db: Kysely ctx.reply(LangManager.getLang(ctx.msg.from.language_code) .replies.captcha.failed(TIMEOUT_AFTER_FAIL_MINS)) ctx.api.deleteMessage(ctx.chatId, ctx.session.captcha_data!.message_id).catch() + // TODO: Add timeout } } return diff --git a/bot/normal_mode/init.ts b/bot/normal_mode/init.ts index 30ce842..7eeb0e7 100644 --- a/bot/normal_mode/init.ts +++ b/bot/normal_mode/init.ts @@ -1,5 +1,5 @@ import { Bot } from "https://deno.land/x/grammy/mod.ts"; -import { Kysely } from "npm:kysely"; +import { Kysely } from 'npm:kysely'; import { Ctx } from "../ctx.ts"; import { CompiledConfig } from "../../cfg/config.ts"; import { Database } from "../../repo/exports.ts"; @@ -9,6 +9,7 @@ import { checkUserOnStart } from "./user_managment.ts"; export const init = (bot: Bot, db: Kysely, cfg: CompiledConfig) => { + console.log(`initializing normal mode`) const { botCfg } = cfg bot.on('message', ctx => { @@ -26,13 +27,14 @@ export const init = (bot: Bot, db: Kysely, cfg: CompiledConfig) = }) - bot.on('message:text', ctx => { + bot.on('message:text', async ctx => { + console.log(`From: ${ctx.from.id} Message: ${ctx.msg.text}`) if (ctx.from.is_bot || ctx.hasCommand('start')) return if (ctx.chat.id === botCfg.chat_id) { - checkUser(ctx, db, botCfg, true) + await checkUser(ctx, db, botCfg, true) } else if (ctx.message.chat.type == "private" && ctx.session.captcha_data) { - checkCaptchaSolution(ctx, db, botCfg) + await checkCaptchaSolution(ctx, db, botCfg) } }) @@ -41,6 +43,8 @@ export const init = (bot: Bot, db: Kysely, cfg: CompiledConfig) = if (!ctx.from || ctx.from.is_bot) return if (ctx.chat.type !== 'private') return + console.log(`Start called by ${ctx.from.id}`) + const userInfo = await checkUserOnStart(ctx, db) if (userInfo.isBlocked || userInfo.isTimeout) return diff --git a/bot/normal_mode/user_managment.ts b/bot/normal_mode/user_managment.ts index beee407..18ecdbe 100644 --- a/bot/normal_mode/user_managment.ts +++ b/bot/normal_mode/user_managment.ts @@ -1,10 +1,10 @@ import { CommandContext, Filter } from "https://deno.land/x/grammy/mod.ts"; -import { Kysely, Transaction } from "npm:kysely"; +import { Kysely, Transaction } from 'npm:kysely'; import { Ctx } from "../ctx.ts"; import { Database } from "../../repo/exports.ts"; import { checkUserRestrictions } from "../../core/users.ts"; import { UserCheckedRestrictions } from "../../core/entities.ts"; -import { BotConfig } from "../../cfg/bot.ts"; +import { BotConfig } from "../../cfg/config.ts"; export interface CheckUserOut extends UserCheckedRestrictions { diff --git a/cfg/bot.ts b/cfg/bot.ts deleted file mode 100644 index 961455f..0000000 --- a/cfg/bot.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum BotMode{ setup, normal } -export type BotConfig = { - mode: BotMode, - bot_token: string, - chat_id: number, - admin_ids: number[], -} \ No newline at end of file diff --git a/cfg/config.ts b/cfg/config.ts index 37a59b1..88559b0 100644 --- a/cfg/config.ts +++ b/cfg/config.ts @@ -1,16 +1,23 @@ -import type { PoolConfig } from "npm:@types/pg"; +import type * as pg from "npm:pg"; import { strToBool } from "../utils/convert.ts"; import { BadConfigErr } from "../utils/errors.ts"; import { readJsonFileSync } from "../utils/io.ts"; -import { BotConfig } from "./bot.ts"; +import * as path from "jsr:@std/path"; -export const POSTGRES_PASSWORD = "POSTGRES_PASSWORD" +export const POSTGRES_PASSWORD = "POSTGRES_PASSWORD" export const MIC_APPLY_MIGRATIONS = "MIC_APPLY_MIGRATIONS" -export const DEFAULT_CONFIG_FILE_PATH = "DEFAULT_CONFIG_FILE_PATH" +export const DEFAULT_CONFIG_FILE_PATH = import.meta.dirname ? path.join(import.meta.dirname, '../config.json') : '/app/config.json' export const MIC_DROP_DB = "MIC_DROP_DB" export const BOT_TOKEN = "BOT_TOKEN" export const MIC_CONFIG_PATH = "MIC_CONFIG_PATH" +export enum BotMode{ setup, normal } +export type BotConfig = { + mode: BotMode, + bot_token: string, + chat_id: number, + admin_ids: number[], +} export interface EnvConfig { db_password?: string, @@ -50,7 +57,7 @@ export interface MigrationConfig { } export interface CompiledConfig { - pgPoolCfg: PoolConfig + pgPoolCfg: pg.PoolConfig botCfg: BotConfig migrationCfg?: MigrationConfig } @@ -81,7 +88,7 @@ export const getConfig = (): Config => { if (fileCfg.db_password_file) { const decoder = new TextDecoder("utf-8") const data = Deno.readFileSync(fileCfg.db_password_file) - db_password = decoder.decode(data).toString().slice(0, -1) + db_password = decoder.decode(data).toString() } else if (envCfg.db_password) { db_password = envCfg.db_password } else throw new BadConfigErr( @@ -112,6 +119,4 @@ export const getConfig = (): Config => { bot_token, } -} - -export * from "./bot.ts" \ No newline at end of file +} \ No newline at end of file diff --git a/config.ts b/config.ts index 454e9ba..f472111 100644 --- a/config.ts +++ b/config.ts @@ -1,7 +1,6 @@ import type { PoolConfig } from "npm:@types/pg"; -import { getConfig } from "./cfg/config.ts"; +import { BotMode, getConfig } from "./cfg/config.ts"; import type { BotConfig, CompiledConfig } from "./cfg/config.ts"; -import { BotMode } from "./cfg/bot.ts"; import * as path from "jsr:@std/path"; const MIC_CHAT_ID = -1002438254268 @@ -16,7 +15,7 @@ export const DEFAULT_DB_TLS = false export const loadConfig = (): CompiledConfig => { const cfg = getConfig() const pgPoolCfg: PoolConfig = { - host: cfg.db_name || DEFAULT_DB_HOST, + host: cfg.db_host || DEFAULT_DB_HOST, user: cfg.db_user || DEFAULT_DB_USER, database: cfg.db_name || DEFAULT_DB_NAME, port: cfg.db_port || DEFAULT_DB_PORT, @@ -36,7 +35,8 @@ export const loadConfig = (): CompiledConfig => { migrationCfg: { dropDb: cfg.drop_db, applyMigrations: cfg.apply_migrations, - migrationsPath: path.join(__dirname, "migrations"), + migrationsPath: import.meta.dirname ? + path.join(import.meta.dirname, 'migrations') : '/app/migrations', } } } \ No newline at end of file diff --git a/core/users.ts b/core/users.ts index ff49dbf..ee9933a 100644 --- a/core/users.ts +++ b/core/users.ts @@ -1,4 +1,4 @@ -import { Kysely, Transaction } from "npm:kysely"; +import { Kysely, Transaction } from 'npm:kysely'; import { Database } from "../repo/exports.ts"; import { UserCheckedRestrictions, UserRestrictionsInfo } from "./entities.ts"; diff --git a/deno.lock b/deno.lock index a7589cf..1924e71 100644 --- a/deno.lock +++ b/deno.lock @@ -193,7 +193,7 @@ "https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression-builder.js": "da9c44985b130c4d9ebd2dabff36b66e748e5afa8a785313e0d9318967ef24d7", "https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression-wrapper.js": "35742f42558a1cbb369ecf7b3cb863f4486fa0c2384fb23e278244440f3c12c9", "https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression.js": "7c024b0c9f292bbd0aefe486c81328d7b18ece24cb569afc3171e06035f4f970", - "https://cdn.jsdelivr.net/npm/kysely/dist/esm/index.js": "ba2b09d8e5e4ae121ef77cfd971477858381650c26bcf28dfd3a8e5ed0535e8a", + "npm:kysely": "ba2b09d8e5e4ae121ef77cfd971477858381650c26bcf28dfd3a8e5ed0535e8a", "https://cdn.jsdelivr.net/npm/kysely/dist/esm/kysely.js": "d6b10df0da4bb8d853d1ff946b8a1a5e4c254c7a2e26d3a5ecacce82c9e3c7eb", "https://cdn.jsdelivr.net/npm/kysely/dist/esm/migration/file-migration-provider.js": "04be6f4d0bb587f254b270875b529bbf30bbdf19af2d95b06272cd5e6b01e56e", "https://cdn.jsdelivr.net/npm/kysely/dist/esm/migration/migrator.js": "35c171e46ae9d18b9b9459148f626be5bc88f22ee5f7844b9b6818dd53ecb77a", diff --git a/docker-compose.yml b/docker-compose.yml index 6f3aaee..34a0021 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,8 @@ services: restart: always depends_on: - postgres + environment: + - MIC_APPLY_MIGRATIONS=y networks: - mic_bot secrets: diff --git a/main.ts b/main.ts index e22bd23..80c2b8a 100644 --- a/main.ts +++ b/main.ts @@ -3,14 +3,14 @@ import { init as initNormalMode } from "./bot/normal_mode/init.ts" import { init as initSetupMode } from "./bot/setup_mode/init.ts" import { setupDB } from "./repo/exports.ts"; import { loadConfig } from "./config.ts"; -import { BotMode } from "./cfg/bot.ts"; +import { BotMode } from "./cfg/config.ts"; const main = async () => { const cfg = loadConfig() const { botCfg } = cfg const { db } = await setupDB(cfg) - runBot(botCfg, (bot) => { + await runBot(botCfg, (bot) => { switch (botCfg.mode) { case BotMode.normal: initNormalMode(bot, db, cfg) diff --git a/repo/setup_db.ts b/repo/setup_db.ts index 911cef4..66ae2af 100644 --- a/repo/setup_db.ts +++ b/repo/setup_db.ts @@ -3,37 +3,61 @@ import { Migrator, PostgresDialect, FileMigrationProvider -} from 'https://cdn.jsdelivr.net/npm/kysely/dist/esm/index.js' +} from 'npm:kysely' /// -// @deno-types="npm:@types/pg" -import { Pool } from "npm:pg"; +import pg from "npm:pg"; import { Database } from "./scheme.ts" import { CompiledConfig } from "../cfg/config.ts"; -import { promises as fs } from 'node:fs' -import * as path from 'node:path' - export type SetupDbOutput = { - pool: Pool, + pool: pg.Pool, db: Kysely, } export const setupDB = async (cfg: CompiledConfig): Promise => { - const pool = new Pool(cfg.pgPoolCfg) + console.log("creating db instance") + const pool = new pg.Pool(cfg.pgPoolCfg) const dialect = new PostgresDialect({ pool }) const db = new Kysely({ dialect }) - if (cfg.migrationCfg && cfg.migrationCfg.applyMigrations) { + if (cfg.migrationCfg?.applyMigrations) { + console.log("applying db migrations", cfg.migrationCfg.migrationsPath) const migrator = new Migrator({ db, provider: new FileMigrationProvider({ - fs, - path, + fs: { + readdir(path) { + return Promise.resolve( + [...Deno.readDirSync(path)].map((file) => file.name) + ) + }, + }, + path: { + join(...path) { + return path.join('/') + }, + }, migrationFolder: cfg.migrationCfg.migrationsPath, }), allowUnorderedMigrations: true }) - await migrator.migrateToLatest() + + const { error, results } = await migrator.migrateToLatest() + + results?.forEach((it) => { + if (it.status === 'Success') { + console.log(`migration "${it.migrationName}" was executed successfully`) + } else if (it.status === 'Error') { + console.error(`failed to execute migration "${it.migrationName}"`) + } + }) + + if (error) { + console.error('failed to migrate') + console.error(error) + throw error + } } + return { pool, db } } \ No newline at end of file