diff --git a/.dockerignore b/.dockerignore index 0f71be8..5f2411b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ data/ham-dynamic.txt data/spam-dynamic.txt data/tg-spam.db* -docker-compose*.yml \ No newline at end of file +docker-compose*.yml +var/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 91993d9..780f6c5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ tg-spam.db-wal logs/ site/public/ _examples/simplechat/messages.db -*.loaded \ No newline at end of file +*.loaded +var/ \ No newline at end of file diff --git a/app/main.go b/app/main.go index 1bb3144..f7b1861 100644 --- a/app/main.go +++ b/app/main.go @@ -198,8 +198,8 @@ func execute(ctx context.Context, opts options) error { log.Print("[WARN] dry mode, no actual bans") } - concertOnly := opts.Convert == "only" - if !opts.Server.Enabled && !concertOnly && (opts.Telegram.Token == "" || opts.Telegram.Group == "") { + convertOnly := opts.Convert == "only" + if !opts.Server.Enabled && !convertOnly && (opts.Telegram.Token == "" || opts.Telegram.Group == "") { return errors.New("telegram token and group are required") } @@ -220,6 +220,16 @@ func execute(ctx context.Context, opts options) error { // make detector with all sample files loaded detector := makeDetector(opts) + // make spam bot + spamBot, err := makeSpamBot(ctx, opts, dataDB, detector) + if err != nil { + return fmt.Errorf("can't make spam bot, %w", err) + } + if opts.Convert == "only" { + log.Print("[WARN] convert only mode, converting text samples and exit") + return nil + } + // make store and load approved users approvedUsersStore, auErr := storage.NewApprovedUsers(ctx, dataDB) if auErr != nil { @@ -232,16 +242,6 @@ func execute(ctx context.Context, opts options) error { } log.Printf("[DEBUG] approved users from: %s, loaded: %d", dbFile, count) - // make spam bot - spamBot, err := makeSpamBot(ctx, opts, dataDB, detector) - if err != nil { - return fmt.Errorf("can't make spam bot, %w", err) - } - if opts.Convert == "only" { - log.Print("[WARN] convert only mode, converting text samples and exit") - return nil - } - // make locator locator, err := storage.NewLocator(ctx, opts.HistoryDuration, opts.HistoryMinSize, dataDB) if err != nil { @@ -370,9 +370,9 @@ func activateServer(ctx context.Context, opts options, sf *bot.SpamFilter, loc * } // make store and load approved users - detectedSpamStore, auErr := storage.NewDetectedSpam(ctx, db) - if auErr != nil { - return fmt.Errorf("can't make approved users store, %w", auErr) + detectedSpamStore, dsErr := storage.NewDetectedSpam(ctx, db) + if dsErr != nil { + return fmt.Errorf("can't make detected spam store, %w", dsErr) } settings := webapi.Settings{ diff --git a/app/storage/approved_users.go b/app/storage/approved_users.go index 59ab5e1..9ad1ba1 100644 --- a/app/storage/approved_users.go +++ b/app/storage/approved_users.go @@ -46,19 +46,27 @@ func NewApprovedUsers(ctx context.Context, db *Engine) (*ApprovedUsers, error) { return nil, fmt.Errorf("db connection is nil") } - // create schema in a single transaction - tx, err := db.Begin() + var exists int + err := db.Get(&exists, "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='approved_users'") if err != nil { - return nil, fmt.Errorf("failed to start transaction: %w", err) + return nil, fmt.Errorf("failed to check for approved_users table existence: %w", err) } - defer tx.Rollback() - if _, err = tx.ExecContext(ctx, approvedUsersSchema); err != nil { - return nil, fmt.Errorf("failed to create schema: %w", err) - } + // create schema in a single transaction if the table does not exist + if exists == 0 { + tx, err := db.Begin() + if err != nil { + return nil, fmt.Errorf("failed to start transaction: %w", err) + } + defer tx.Rollback() - if err = tx.Commit(); err != nil { - return nil, fmt.Errorf("failed to commit transaction: %w", err) + if _, err = tx.ExecContext(ctx, approvedUsersSchema); err != nil { + return nil, fmt.Errorf("failed to create schema: %w", err) + } + + if err = tx.Commit(); err != nil { + return nil, fmt.Errorf("failed to commit transaction: %w", err) + } } if err := migrateTable(&db.DB, db.GID()); err != nil { @@ -208,7 +216,7 @@ func migrateTable(db *sqlx.DB, gid string) error { if err := tx.Commit(); err != nil { return fmt.Errorf("failed to commit migration: %w", err) } - log.Printf("[DEBUG] approved_users table migrated") + log.Printf("[DEBUG] approved_users table migrated, records: %d", len(rows)) return nil } diff --git a/app/storage/detected_spam.go b/app/storage/detected_spam.go index 6bdec1c..09d5fff 100644 --- a/app/storage/detected_spam.go +++ b/app/storage/detected_spam.go @@ -54,20 +54,31 @@ func NewDetectedSpam(ctx context.Context, db *Engine) (*DetectedSpam, error) { return nil, fmt.Errorf("db connection is nil") } - tx, err := db.Begin() + // first check if the table exists. we can't do this in a transaction + // because missing columns will cause the transaction to fail + var exists int + err := db.Get(&exists, "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='detected_spam'") if err != nil { - return nil, fmt.Errorf("failed to start transaction: %w", err) + return nil, fmt.Errorf("failed to check for detected_spam table existence: %w", err) } - defer tx.Rollback() - if _, err = tx.ExecContext(ctx, detectedSpamSchema); err != nil { - return nil, fmt.Errorf("failed to create schema: %w", err) - } + if exists == 0 { // table does not exist, create it + tx, err := db.Begin() + if err != nil { + return nil, fmt.Errorf("failed to start transaction: %w", err) + } + defer tx.Rollback() + + if _, err = tx.ExecContext(ctx, detectedSpamSchema); err != nil { + return nil, fmt.Errorf("failed to create schema: %w", err) + } - if err = tx.Commit(); err != nil { - return nil, fmt.Errorf("failed to commit transaction: %w", err) + if err = tx.Commit(); err != nil { + return nil, fmt.Errorf("failed to commit transaction: %w", err) + } } + // migrate detected_spam table if err := migrateDetectedSpam(&db.DB, db.GID()); err != nil { return nil, fmt.Errorf("failed to migrate detected_spam: %w", err) } @@ -151,10 +162,15 @@ func migrateDetectedSpam(db *sqlx.DB, gid string) error { } // update existing records with the provided gid - if _, err = db.Exec("UPDATE detected_spam SET gid = ? WHERE gid = ''", gid); err != nil { + res, err := db.Exec("UPDATE detected_spam SET gid = ? WHERE gid = ''", gid) + if err != nil { return fmt.Errorf("failed to update gid for existing records: %w", err) } + rowsAffected, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } - log.Printf("[DEBUG] detected_spam table migrated, gid updated to %q", gid) + log.Printf("[DEBUG] detected_spam table migrated, gid updated to %q, records: %d", gid, rowsAffected) return nil }