Refactor and enhance database connection and data validation

- Refactored the entry-point to bypass first run setup if valid configuration information already exists.
- Enhanced data validation before inserting into the database.
- Ensured correct data placement in the appropriate tables and columns in the database.
- Added logging for database connection attempts and results.
- Fixed a bug in the speed conversion from Gbps to Mbps.
This commit is contained in:
VetheonGames 2023-06-29 18:27:08 -06:00
parent cb4cec747b
commit b3dbd0f07c
5 changed files with 103 additions and 40 deletions

View File

@ -11,7 +11,7 @@ require_relative '../lib/utils/logg_man'
include Utilities # rubocop:disable Style/MixinUsage include Utilities # rubocop:disable Style/MixinUsage
# binding.b(do: 'irb') # binding.b(do: 'irb')
loggman = LoggMan.new @loggman = LoggMan.new
# Create .env file if it doesn't exist # Create .env file if it doesn't exist
File.open('.env', 'w') {} unless File.exist?('.env') File.open('.env', 'w') {} unless File.exist?('.env')
@ -20,7 +20,7 @@ File.open('.env', 'w') {} unless File.exist?('.env')
Dotenv.load Dotenv.load
# Initialize DatabaseManager # Initialize DatabaseManager
db_manager = DatabaseManager.new db_manager = DatabaseManager.new(@loggman)
# Get database details from environment variables # Get database details from environment variables
db_details = { db_details = {
@ -30,34 +30,50 @@ db_details = {
database: ENV['DB_DATABASE'] database: ENV['DB_DATABASE']
} }
# Decrypt password
dec_pass = decrypt_string_chacha20(db_details[:password], db_details[:key])
# If any of the necessary details are missing, run the first run setup # If any of the necessary details are missing, run the first run setup
if db_details.values.any?(&:nil?) if db_details.values.any?(&:nil?)
loggman.log_warn('Missing or incomplete configuration. Running first run setup.') @loggman.log_warn('Missing or incomplete configuration. Running first run setup.')
first_run_init = FirstRunInit.new(db_manager) first_run_init = FirstRunInit.new(@loggman, db_manager)
first_run_init.run first_run_init.run
# Reload environment variables after first run setup # Reload environment variables after first run setup
Dotenv.load Dotenv.load
username = ENV['DB_USERNAME'] db_details = {
password = ENV['DB_PASSWORD'] username: ENV['DB_USERNAME'],
key = ENV['DB_SECRET_KEY'] password: ENV['DB_PASSWORD'],
database = ENV['DB_DATABASE'] key: ENV['DB_SECRET_KEY'],
database: ENV['DB_DATABASE']
}
# Decrypt password again after potentially updating config
dec_pass = decrypt_string_chacha20(db_details[:password], db_details[:key])
end end
# Test connection # Test connection
dec_pass = decrypt_string_chacha20(password, key) unless db_manager.test_db_connection(db_details[:username], dec_pass, db_details[:database])
unless db_manager.test_db_connection(username, dec_pass, database) @loggman.log_warn('Failed to connect to the database with existing configuration. Please re-enter your details.')
loggman.log_warn('Failed to connect to the database with existing configuration. Please re-enter your details.') first_run_init = FirstRunInit.new(@loggman, db_manager)
first_run_init = FirstRunInit.new(db_manager)
first_run_init.run first_run_init.run
# Reload environment variables after potentially updating config
Dotenv.load
db_details = {
username: ENV['DB_USERNAME'],
password: ENV['DB_PASSWORD'],
key: ENV['DB_SECRET_KEY'],
database: ENV['DB_DATABASE']
}
# Decrypt password again after potentially updating config
dec_pass = decrypt_string_chacha20(db_details[:password], db_details[:key])
end end
# Test connection again after potentially updating config # Test connection again after potentially updating config
if db_manager.test_db_connection(username, dec_pass, database) if db_manager.test_db_connection(db_details[:username], dec_pass, db_details[:database])
loggman.log_info('Successfully connected to the database.') @loggman.log_info('Successfully connected to the database.')
else else
loggman.log_error('Failed to connect to the database. Please check your configuration.') @loggman.log_error('Failed to connect to the database. Please check your configuration.')
exit 1 exit 1
end end
puts 'Program successfully ran with no errors' @loggman.log_warn('Program successfully ran with no errors')
# TODO: Add the rest of your application logic here # TODO: Add the rest of application logic here

View File

@ -4,20 +4,18 @@ require 'sequel'
require 'mysql2' require 'mysql2'
require_relative 'system_information_gather' require_relative 'system_information_gather'
require_relative '../utils/utilities' require_relative '../utils/utilities'
require_relative 'logg_man'
# database manager # database manager
class DatabaseManager class DatabaseManager
include Utilities include Utilities
def initialize def initialize(logger)
@db = nil @db = nil
@loggman = LoggMan.new @loggman = logger
end end
def test_db_connection(username, password, database) # rubocop:disable Metrics/MethodLength def test_db_connection(username, password, database) # rubocop:disable Metrics/MethodLength
loggman = LoggMan.new @loggman.log_info('Attempting to connect to the database...')
loggman.log_info('Attempting to connect to the database...')
display_alert('Attempting to connect to the database...', :info) display_alert('Attempting to connect to the database...', :info)
# Create the connection string # Create the connection string
@ -25,11 +23,11 @@ class DatabaseManager
@db = Sequel.connect(connection_string) @db = Sequel.connect(connection_string)
# Try a simple query to test the connection # Try a simple query to test the connection
@db.run 'SELECT 1' @db.run 'SELECT 1'
loggman.log_info('Successfully connected to the database.') @loggman.log_info('Successfully connected to the database.')
display_alert('Successfully connected to the database.', :info) display_alert('Successfully connected to the database.', :info)
true true
rescue Sequel::DatabaseConnectionError => e rescue Sequel::DatabaseConnectionError => e
loggman.log_error("Failed to connect to the database: #{e.message}") @loggman.log_error("Failed to connect to the database: #{e.message}")
display_alert('Failed to connect to the database!', :error) display_alert('Failed to connect to the database!', :error)
false false
end end
@ -63,9 +61,26 @@ class DatabaseManager
end end
end end
def store_system_info(system_info) def store_system_info(system_info) # rubocop:disable Metrics/MethodLength
# Check if the system_info already exists in the database
@loggman.log_info('Checking if info exists in the Database...')
existing_system_info = @db[:system_info].where(uplink_speed: system_info[:uplink_speed],
downlink_speed: system_info[:downlink_speed],
total_bandwidth: system_info[:total_bandwidth]).first
if existing_system_info
# If it exists, update it
@loggman.log_info('Info already exists. Updating instead of adding more data to the table...')
@db[:system_info].where(id: existing_system_info[:id]).update(system_info)
else
# If it doesn't exist, insert it
@loggman.log_info('Info does not exist already, inserting it...')
@db[:system_info].insert(system_info) @db[:system_info].insert(system_info)
end end
end
def create_services_table def create_services_table
@db.create_table? :services do @db.create_table? :services do
@ -75,9 +90,24 @@ class DatabaseManager
end end
end end
def store_services(services) def store_services(services) # rubocop:disable Metrics/MethodLength
services.each do |service| services.each do |service|
# Check if the service already exists in the database
@loggman.log_info('Checking if info exists in the Database...')
existing_service = @db[:services].where(service_name: service).first
if existing_service
# If it exists, update it
@loggman.log_info('Info already exists, updating instead of adding more data to the table...')
@db[:services].where(id: existing_service[:id]).update(service_name: service, status: true)
else
# If it doesn't exist, insert it
@loggman.log_info('Info does not exist already, inserting it...')
@db[:services].insert(service_name: service, status: true) @db[:services].insert(service_name: service, status: true)
end end
end end
end end
end

View File

@ -6,17 +6,16 @@ require 'dotenv'
require_relative 'database_manager' require_relative 'database_manager'
require_relative 'system_information_gather' require_relative 'system_information_gather'
require_relative 'utilities' require_relative 'utilities'
require_relative 'logg_man'
# first run class # first run class
class FirstRunInit class FirstRunInit
include Utilities include Utilities
include Curses include Curses
def initialize(db_manager = nil) def initialize(logger, db_manager = nil)
@db_manager = db_manager || DatabaseManager.new @db_manager = db_manager || DatabaseManager.new(logger)
@info_gatherer = SystemInformationGather.new(@db_manager) @info_gatherer = SystemInformationGather.new(@db_manager, logger)
@loggman = LoggMan.new @loggman = logger
Dotenv.load Dotenv.load
end end
@ -57,33 +56,40 @@ class FirstRunInit
end end
def ask_for_db_details # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def ask_for_db_details # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@loggman.log_info('Asking for Database details...')
Curses.clear Curses.clear
Curses.setpos(1, 0) Curses.setpos(1, 0)
Curses.addstr('Please enter your database username: ') Curses.addstr('Please enter your database username: ')
Curses.refresh Curses.refresh
username = DCI.catch_input(true) username = DCI.catch_input(true)
@loggman.log_info('Database Username entered!')
Curses.setpos(2, 0) Curses.setpos(2, 0)
Curses.addstr('Please enter your database password: ') Curses.addstr('Please enter your database password: ')
Curses.refresh Curses.refresh
Curses.noecho Curses.noecho
password = DCI.catch_input(false) password = DCI.catch_input(false)
@loggman.log_info('Database Password Stored Securely!')
Curses.echo Curses.echo
Curses.setpos(3, 0) Curses.setpos(3, 0)
Curses.addstr('Please enter your database name: ') Curses.addstr('Please enter your database name: ')
Curses.refresh Curses.refresh
database = DCI.catch_input(true) database = DCI.catch_input(true)
@loggman.log_info('Database Name entered!')
# Generate a secret key # Generate a secret key
key = generate_key key = generate_key
@loggman.log_info('Secret Key Generated!')
# Encrypt the password # Encrypt the password
encrypted_password = encrypt_string_chacha20(password, key) encrypted_password = encrypt_string_chacha20(password, key)
@loggman.log_info('Password Encrypted!')
db_details = { username:, password: encrypted_password, key:, database: } db_details = { username:, password: encrypted_password, key:, database: }
write_db_details_to_config_file(db_details) write_db_details_to_config_file(db_details)
@loggman.log_info('Wiriting Database details to a file!')
end end
def write_db_details_to_config_file(db_details) def write_db_details_to_config_file(db_details)
@ -95,8 +101,10 @@ class FirstRunInit
file.puts %(DB_DATABASE="#{db_details[:database]}") file.puts %(DB_DATABASE="#{db_details[:database]}")
end end
@loggman.log_info('Database details saved! Reloading environment...')
# Load the .env file using dotenv # Load the .env file using dotenv
Dotenv.load Dotenv.load
@loggman.log_info('Environment restarted!')
rescue StandardError => e rescue StandardError => e
@loggman.log_error("Failed to write to .env file: #{e.message}") @loggman.log_error("Failed to write to .env file: #{e.message}")
end end

View File

@ -10,8 +10,9 @@ require 'dynamic_curses_input'
class SystemInformationGather class SystemInformationGather
include Utilities include Utilities
def initialize(db_manager) def initialize(db_manager, logger)
@db_manager = db_manager @db_manager = db_manager
@loggman = logger
end end
def gather_system_info # rubocop:disable Metrics/MethodLength def gather_system_info # rubocop:disable Metrics/MethodLength
@ -43,7 +44,7 @@ class SystemInformationGather
Curses.refresh Curses.refresh
Curses.addstr('Uplink Speed: ') Curses.addstr('Uplink Speed: ')
speed = DCI.catch_input(true) speed = DCI.catch_input(true)
return speed.end_with?('gbps') ? convert_speed_to_mbps(speed) : speed.to_i if valid_speed?(speed) return convert_speed_to_mbps(speed) if valid_speed?(speed)
Curses.setpos(5, 0) Curses.setpos(5, 0)
Curses.addstr("Whoops! That didn't appear to be a valid speed. Please try again!") Curses.addstr("Whoops! That didn't appear to be a valid speed. Please try again!")
@ -60,7 +61,7 @@ class SystemInformationGather
Curses.refresh Curses.refresh
Curses.addstr('Downlink Speed: ') Curses.addstr('Downlink Speed: ')
speed = DCI.catch_input(true) speed = DCI.catch_input(true)
return speed.end_with?('gbps') ? convert_speed_to_mbps(speed) : speed.to_i if valid_speed?(speed) return convert_speed_to_mbps(speed) if valid_speed?(speed)
Curses.setpos(5, 0) Curses.setpos(5, 0)
Curses.addstr("Whoops! That didn't appear to be a valid speed. Please try again!") Curses.addstr("Whoops! That didn't appear to be a valid speed. Please try again!")
@ -69,7 +70,7 @@ class SystemInformationGather
end end
def valid_speed?(speed) def valid_speed?(speed)
speed.to_i.positive? speed.to_i.positive? && speed.match?(/\A\d+(gbps|mbps)\z/i)
end end
def ask_for_services # rubocop:disable Metrics/MethodLength def ask_for_services # rubocop:disable Metrics/MethodLength

View File

@ -4,15 +4,23 @@ require 'securerandom'
require 'digest' require 'digest'
require 'base64' require 'base64'
require 'openssl' require 'openssl'
require_relative 'logg_man'
# Utiltiies Module # Utiltiies Module
module Utilities module Utilities
# Converts speed from Gbps to Mbps if necessary # Converts speed from Gbps to Mbps if necessary
def convert_speed_to_mbps(speed) def convert_speed_to_mbps(speed)
return nil unless speed.is_a?(String) && speed.match?(/\A\d+(gbps|mbps)\z/i) return nil unless speed.is_a?(String) && speed.downcase.match?(/\A\d+(gbps|mbps)\z/i)
speed.end_with?('gbps') ? speed.to_i * 1000 : speed.to_i # Extract the numeric part and the unit from the speed
numeric_speed, unit = speed.downcase.match(/(\d+)(gbps|mbps)/i).captures
# Convert the numeric part to an integer
numeric_speed = numeric_speed.to_i
# If the unit is 'gbps', multiply the numeric part by 1000
numeric_speed *= 1000 if unit == 'gbps'
numeric_speed
end end
# Converts an array of services into a hash # Converts an array of services into a hash