using Ophelias.Models; using System.Data.SQLite; namespace Ophelias.Managers { internal class Database : IDisposable { /* * This class provides some basic functions that * mirror SQLite. The reason for this class is to include * some functions on it that typically would be separate. * In another world, these functions could be of another * static class as they don't really serve a purpose other * than a one-time use if the database needs initialization. */ internal SQLiteConnection con = new("DataSource=database.sqlite3;Version=3;"); internal Database() { Connect(); } internal void Connect() { con.Open(); } internal void Close() { con.Close(); con.Dispose(); } internal void InitializeTables() { /* * This string is used to create the tables used by the models * and the database functions. For more details, see the formatting * string below as it has been organized in such a way there it is readable. */ string tableCommands = @"CREATE TABLE IF NOT EXISTS [transactions] ( [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Rate] INTEGER NOT NULL, [Owed] INTEGER NOT NULL, [Penalty] INTEGER NOT NULL, [Multiplier] INTEGER NOT NULL, [RefundAmount] INTEGER NOT NULL, [AmountPaid] INTEGER NOT NULL, [PayBy] TEXT NOT NULL, [LastPaid] TEXT NULL, [PaidOn] TEXT NULL); CREATE TABLE IF NOT EXISTS [reservations] ( [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [RoomNum] INTEGER NULL, [GuestID] INTEGER NOT NULL, [TransactionID] INTEGER NOT NULL UNIQUE, [IsNoShow] BOOLEAN NOT NULL CHECK ([IsNoShow] IN (0,1)), [Type] INTEGER NOT NULL CHECK ([Type] IN (0,1,2,3)), [Status] INTEGER NOT NULL CHECK ([Status] IN (0,1,2,3)), [CreationDate] TEXT NOT NULL, [StartDate] TEXT NOT NULL, [EndDate] TEXT NOT NULL, [CheckIn] TEXT NULL, [CheckOut] TEXT NULL, [DateChanged] TEXT NULL, FOREIGN KEY ([RoomNum]) REFERENCES rooms(ID), FOREIGN KEY ([GuestID]) REFERENCES guests(ID), FOREIGN KEY ([TransactionID]) REFERENCES transactions(ID)); CREATE TABLE IF NOT EXISTS [guests] ( [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Fname] TEXT NOT NULL, [Lname] TEXT NOT NULL, [Email] TEXT NOT NULL UNIQUE, [CreditCard] TEXT NULL UNIQUE, [Expiration] TEXT NULL, [CCV] TEXT NULL); CREATE TABLE IF NOT EXISTS [rooms] ( [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Occupied] BOOLEAN NOT NULL CHECK ([Occupied] IN (0,1))); CREATE TABLE IF NOT EXISTS [rates] ( [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Rate] INTEGER NOT NULL, [DateSet] TEXT NOT NULL UNIQUE, [DefaultRate] INTEGER NULL UNIQUE CHECK ([DefaultRate] IN (1)));"; using SQLiteCommand cmd = con.CreateCommand(); cmd.CommandText = tableCommands; cmd.ExecuteNonQuery(); } internal void InitializeRoomsTable() { /* * This is a one off initialization function to intialize * a list of 45 rooms. The design specification called * for a static 45 rooms that were not configurable hence * it being hardcoded here for now. Ideally this is moved * out to config file. */ using SQLiteCommand cmd = con.CreateCommand(); for (int i = 0; i < 45; i++) { cmd.CommandText = $"INSERT INTO ROOMS (Occupied) VALUES (0);"; cmd.ExecuteNonQuery(); } } public void Dispose() // Needed to support "using" { Close(); } } internal static class QueryBuilder { /* * This class is for building update query strings. To prevent SQL injection * on dynamically built strings, the inputs are now used as null check values, * rather than as null check values that get set if they exist. Parameters are * now used in their place which prevent SQL injection. */ internal static string? UpdateTransaction(int Id, double? Rate = null, double? Owed = null, double? Penalty = null, double? Multiplier = null, double? Refund = null, DateTime? PayBy = null, DateTime? LastPaid = null, DateTime? PaidOn = null, double? AmountPaid = null) { /* * Builds an update query string for the transaction model */ List queryComponents = new(); string query = "UPDATE transactions SET"; if (Rate.HasValue) { queryComponents.Add($"Rate = @Rate"); } if (Owed.HasValue) { queryComponents.Add($"Owed = @Owed"); } if (Penalty.HasValue) { queryComponents.Add($"Penalty = @Penalty"); } if (Multiplier.HasValue) { queryComponents.Add($"Multiplier = @Multiplier"); } if (Refund.HasValue) { queryComponents.Add($"RefundAmount = @RefundAmount"); } if (AmountPaid.HasValue) { queryComponents.Add($"AmountPaid = @AmountPaid"); } if (PayBy.HasValue) { queryComponents.Add($"PayBy = @PayBy"); } if (LastPaid.HasValue) { queryComponents.Add($"LastPaid = @LastPaid"); } if (PaidOn.HasValue) { queryComponents.Add($"PaidOn = @PaidOn"); } if (queryComponents.Count >= 0) { query += " " + string.Join(", ", queryComponents) + " " + $"WHERE ID = @ID;"; } else { return null; } return query; } internal static string? UpdateReservation(int Id, int? RoomID = null, int? GuestID = null, int? TransactionID = null, bool? IsNoShow = null, ReservationType? Type = null, ReservationStatus? Status = null, DateTime? CreationDate = null, DateTime? StartDate = null, DateTime? EndDate = null, DateTime? CheckIn = null, DateTime? CheckOut = null, DateTime? DateChanged = null) { /* * Builds an update query string for the reservation model */ List QueryParts = new(); string query = "UPDATE reservations SET"; if (RoomID.HasValue) { QueryParts.Add($"RoomID = @RID"); } if (GuestID.HasValue) { QueryParts.Add($"GuestID = @GID"); } if (TransactionID.HasValue) { QueryParts.Add($"TransactionID = @TID"); } if (IsNoShow.HasValue) { QueryParts.Add($"IsNoShow = @IsNoShow"); } if (Type.HasValue) { QueryParts.Add($"Type = @Type"); } if (Status.HasValue) { QueryParts.Add($"Status = @Status"); } if (CreationDate.HasValue) { QueryParts.Add($"CreationDate = @CreationDate"); } if (StartDate.HasValue) { QueryParts.Add($"StartDate = @StartDate"); } if (EndDate.HasValue) { QueryParts.Add($"EndDate = @EndDate"); } if (CheckIn.HasValue) { QueryParts.Add($"CheckIn = @CheckIn"); } if (CheckOut.HasValue) { QueryParts.Add($"CheckOut = @CheckOut"); } if (DateChanged.HasValue) { QueryParts.Add($"DateChanged = @DateChanged"); } if (QueryParts.Count >= 0) { query += " " + string.Join(", ", QueryParts) + " " + $"WHERE ID = @ID;"; } else { return null; } return query; } internal static string? UpdateGuest(int Id, string? FirstName = null, string? LastName = null, string? Email = null, string? CreditCard = null, string? Expiration = null, string? CCV = null) { /* * Builds an update query string for the guest model */ List QueryParts = new(); string query = "UPDATE guests SET"; if (!string.IsNullOrEmpty(FirstName)) { QueryParts.Add($"Fname = @Fname"); } if (!string.IsNullOrEmpty(LastName)) { QueryParts.Add($"Lname = @Lname"); } if (!string.IsNullOrEmpty(LastName)) { QueryParts.Add($"Email = @Email"); } if (!string.IsNullOrEmpty(CreditCard)) { QueryParts.Add($"CreditCard = @CreditCard"); } if (!string.IsNullOrEmpty(Expiration)) { QueryParts.Add($"Expiration = @Expiry"); } if (!string.IsNullOrEmpty(CCV)) { QueryParts.Add($"CCV = @CCV"); } if (QueryParts.Count >= 0) { query += " " + string.Join(", ", QueryParts) + " " + $"WHERE ID = @ID;"; } else { return null; } return query; } } }