# FrugalityBot # Copyright (C) 2026 Eri (csxkdv/nxkdv) nxkdv@thenight.club # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . require 'pg' class Database def initialize # Connect once when the bot starts @conn = PG.connect( host: ENV['DB_HOST'] || 'localhost', dbname: ENV['DB_NAME'] || 'fgbot_db', user: ENV['DB_USER'] || 'postgres' ) init_tables end def init_tables sql_wallet = <<~SQL CREATE TABLE IF NOT EXISTS wallets ( user_id BIGINT PRIMARY KEY, amount BIGINT DEFAULT 0 ); SQL sql_ledger = <<~SQL CREATE TABLE IF NOT EXISTS transactions ( id SERIAL PRIMARY KEY, user_id BIGINT NOT NULL, amount BIGINT NOT NULL, reason VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); SQL sql_index = <<~SQL CREATE INDEX IF NOT EXISTS idx_user_history ON transactions(user_id, created_at); SQL @conn.exec(sql_wallet) @conn.exec(sql_ledger) @conn.exec(sql_index) begin @conn.exec("ALTER TABLE wallets ADD COLUMN IF NOT EXISTS locale VARCHAR(5) DEFAULT 'en'") rescue PG::Error => e puts "Migration note: #{e.message}" end puts "Database tables have been initialized." end def get_currency(user_id) # Run the query using parameters ($1) to prevent SQL injection result = @conn.exec_params("SELECT amount FROM wallets WHERE user_id = $1", [user_id]) if result.num_tuples.zero? return 0 # User has no money/row yet else return result[0]['amount'].to_i end end def update_balance(user_id, amount, reason = "transaction") @conn.transaction do @conn.exec_params( "INSERT INTO transactions (user_id, amount, reason) VALUES ($1, $2, $3)", [user_id, amount, reason] ) # We update the user's wallet sql_update = <<~SQL INSERT INTO wallets (user_id, amount) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE SET amount = wallets.amount + $2 SQL @conn.exec_params(sql_update, [user_id, amount]) end end def fetch_report(user_id, interval_string) sql = <<~SQL SELECT SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) as income, SUM(CASE WHEN amount < 0 THEN amount ELSE 0 END) as expenses, SUM(amount) AS net_change FROM transactions WHERE user_id = $1 AND created_at >= NOW() - $2::INTERVAL SQL result = @conn.exec_params(sql, [user_id, interval_string]) row = result[0] { income: row['income'].to_i, expenses: row['expenses'].to_i, net: row['net_change'].to_i } end def get_language(user_id) result = @conn.exec("SELECT locale FROM wallets WHERE user_id = $1", [user_id]) return nil if result.num_tuples.zero? return result[0]['locale'] end def set_language(user_id, locale) sql = <<~SQL INSERT INTO wallets(user_id, amount, locale) VALUES ($1, 0, $2) ON CONFLICT (user_id) DO UPDATE SET locale = $2 SQL @conn.exec(sql, [user_id, locale]) end end