diff --git a/Frolikss.HabitTracker/DatabaseLogic/DatabaseLogic.csproj b/Frolikss.HabitTracker/DatabaseLogic/DatabaseLogic.csproj new file mode 100644 index 00000000..57d8b843 --- /dev/null +++ b/Frolikss.HabitTracker/DatabaseLogic/DatabaseLogic.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/Frolikss.HabitTracker/DatabaseLogic/DatabaseManager.cs b/Frolikss.HabitTracker/DatabaseLogic/DatabaseManager.cs new file mode 100644 index 00000000..54284a38 --- /dev/null +++ b/Frolikss.HabitTracker/DatabaseLogic/DatabaseManager.cs @@ -0,0 +1,328 @@ +using System.Globalization; +using Microsoft.Data.Sqlite; + +namespace DatabaseLogic; + +public class Habit +{ + public int Id { get; set; } + public string Name { get; set; } + public string Units { get; set; } +} + +public class Data +{ + public int Id { get; set; } + public int Quantity { get; set; } + public DateTime? Date { get; set; } + public Habit Habit { get; set; } + + public Data() + { + Habit = new Habit(); + } +} + +public class DatabaseManager +{ + private static string dateFormat = "yyyy-MM-dd"; + private string connectionString; + private string habitTypesTableName = "habit_types"; + private string habitsTableName = "habit_logger"; + + public DatabaseManager(string connectionString) + { + this.connectionString = connectionString; + + ExecuteQuery((command) => + { + command.CommandText = (@$" + CREATE TABLE IF NOT EXISTS {habitTypesTableName} + ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Name TEXT, + Units TEXT + ); + CREATE TABLE IF NOT EXISTS {habitsTableName} + ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + HabitTypeId INTEGER, + Quantity INTEGER, + Date TEXT, + FOREIGN KEY (HabitTypeId) REFERENCES {habitTypesTableName}(Id) + );"); + }); + SeedHabitsTypes(); + SeedHabitLogs(); + } + + void ExecuteQuery(Action? configure = null) + { + try + { + using (var connection = new SqliteConnection(connectionString)) + { + connection.Open(); + + using var pragma = connection.CreateCommand(); + pragma.CommandText = @"PRAGMA foreign_keys = ON;"; + pragma.ExecuteNonQuery(); + + var command = connection.CreateCommand(); + configure?.Invoke(command); + + command.ExecuteNonQuery(); + } + } + catch (Exception err) + { + Console.WriteLine($"Error occured: {err.Message}"); + } + } + + void ExecuteReader(Action configure) + { + using var connection = new SqliteConnection(connectionString); + connection.Open(); + + using var pragma = connection.CreateCommand(); + pragma.CommandText = "PRAGMA foreign_keys = ON;"; + pragma.ExecuteNonQuery(); + + using var command = connection.CreateCommand(); + configure(command); + } + + T ExecuteScalar(Func configure) + { + using var connection = new SqliteConnection(connectionString); + connection.Open(); + + using var pragma = connection.CreateCommand(); + pragma.CommandText = "PRAGMA foreign_keys = ON;"; + pragma.ExecuteNonQuery(); + + using var command = connection.CreateCommand(); + return configure(command); + } + + public int? AddHabit(Habit habit) + { + int? habitId = null; + var commandText = $@" + INSERT INTO {habitTypesTableName} (Name, Units) + VALUES (@name, @units); + SELECT last_insert_rowid(); + "; + + habitId = ExecuteScalar((command) => + { + command.CommandText = commandText; + + command.Parameters.AddWithValue("@name", habit.Name); + command.Parameters.AddWithValue("@units", habit.Units); + + return Convert.ToInt32(command.ExecuteScalar()); + }); + + return habitId; + } + + public List GetAllHabits() + { + var habits = new List(); + + var commandText = $@" + SELECT * FROM {habitTypesTableName} + "; + + ExecuteQuery((command) => + { + command.CommandText = commandText; + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var habit = new Habit(); + + var habitId = reader.GetInt32(0); + var name = reader.GetString(1); + var units = reader.GetString(2); + + habit.Id = habitId; + habit.Name = name; + habit.Units = units; + + habits.Add(habit); + } + } + }); + + return habits; + } + + public void AddRecord(Data data) + { + var commandText = @$" + INSERT INTO {habitsTableName} (Quantity, Date, HabitTypeId) + VALUES (@quantity, @date, @habitTypeId); + "; + + ExecuteQuery((command) => + { + command.CommandText = commandText; + command.Parameters.AddWithValue("@quantity", data.Quantity); + command.Parameters.AddWithValue("@date", data.Date?.ToString(dateFormat)); + command.Parameters.AddWithValue("@habitTypeId", data.Habit.Id); + }); + } + + public List GetAllRecords() + { + var allRecords = new List(); + + var commandText = @$" + SELECT + h.Id AS HabitLogId, + h.HabitTypeId, + h.Quantity, + h.Date, + t.Id AS HabitId, + t.Name AS HabitName, + t.Units AS HabitUnits + FROM {habitsTableName} h + INNER JOIN {habitTypesTableName} t + ON h.HabitTypeId = t.Id; + "; + + ExecuteReader((command) => + { + command.CommandText = commandText; + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var dataItem = new Data(); + + var id = reader.GetInt32(reader.GetOrdinal("HabitLogId")); + dataItem.Id = id; + + var habitId = reader.GetInt32(reader.GetOrdinal("HabitId")); + dataItem.Habit.Id = habitId; + + var quantity = reader.GetInt32(reader.GetOrdinal("Quantity")); + dataItem.Quantity = quantity; + + var stringDate = reader.GetString(reader.GetOrdinal("Date")); + + if (DateTime.TryParseExact( + stringDate, + dateFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var parsedDate)) + { + dataItem.Date = parsedDate; + } + + var habitName = reader.GetString(reader.GetOrdinal("HabitName")); + dataItem.Habit.Name = habitName; + + var habitUnits = reader.GetString(reader.GetOrdinal("HabitUnits")); + dataItem.Habit.Units = habitUnits; + + allRecords.Add(dataItem); + } + } + }); + + return allRecords; + } + + public void UpdateRecord(Data data) + { + var commandText = $@" + UPDATE {habitsTableName} + SET Quantity = @quantity, + Date = @date, + HabitTypeId = @habitTypeId + WHERE Id = @id + "; + + ExecuteQuery((command) => + { + command.CommandText = commandText; + command.Parameters.AddWithValue("@quantity", data.Quantity); + command.Parameters.AddWithValue("@date", data.Date?.ToString(dateFormat)); + command.Parameters.AddWithValue("@habitTypeId", data.Habit.Id); + command.Parameters.AddWithValue("@id", data.Id); + }); + } + + public void DeleteRecord(int recordId) + { + var commandText = $@" + DELETE FROM {habitsTableName} + WHERE id = @id + "; + + ExecuteQuery((command) => + { + command.CommandText = commandText; + command.Parameters.AddWithValue("@id", recordId); + }); + } + + void SeedHabitsTypes() + { + ExecuteQuery(command => + { + command.CommandText = @" + INSERT OR IGNORE INTO habit_types (Id, Name, Units) VALUES + (1, 'Water', 'liters'), + (2, 'Running', 'km'), + (3, 'Reading', 'pages'), + (4, 'Workout', 'minutes'); + "; + }); + } + + void SeedHabitLogs() + { + var count = ExecuteScalar(command => + { + command.CommandText = $"SELECT COUNT(*) FROM {habitsTableName};"; + return Convert.ToInt32(command.ExecuteScalar()); + }); + + if (count > 0) + return; + + var random = new Random(); + var startDate = DateTime.Today.AddDays(-100); + + for (int i = 0; i < 100; i++) + { + var habitTypeId = random.Next(1, 5); + var quantity = random.Next(1, 11); + var date = startDate.AddDays(i); + + ExecuteQuery(command => + { + command.CommandText = $@" + INSERT INTO {habitsTableName} + (HabitTypeId, Quantity, Date) + VALUES + (@habitTypeId, @quantity, @date); + "; + + command.Parameters.AddWithValue("@habitTypeId", habitTypeId); + command.Parameters.AddWithValue("@quantity", quantity); + command.Parameters.AddWithValue("@date", date.ToString(dateFormat)); + }); + } + } + +} \ No newline at end of file diff --git a/Frolikss.HabitTracker/HabitTracker.sln b/Frolikss.HabitTracker/HabitTracker.sln new file mode 100644 index 00000000..f30eff8b --- /dev/null +++ b/Frolikss.HabitTracker/HabitTracker.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HabitTracker", "HabitTracker\HabitTracker.csproj", "{1D4177F4-2BC8-48DE-8FDD-FE05D56EF9C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatabaseLogic", "DatabaseLogic\DatabaseLogic.csproj", "{D5BCE2B7-7E94-4E88-8AEE-D4ADD19AFBC1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D4177F4-2BC8-48DE-8FDD-FE05D56EF9C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D4177F4-2BC8-48DE-8FDD-FE05D56EF9C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D4177F4-2BC8-48DE-8FDD-FE05D56EF9C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D4177F4-2BC8-48DE-8FDD-FE05D56EF9C3}.Release|Any CPU.Build.0 = Release|Any CPU + {D5BCE2B7-7E94-4E88-8AEE-D4ADD19AFBC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5BCE2B7-7E94-4E88-8AEE-D4ADD19AFBC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5BCE2B7-7E94-4E88-8AEE-D4ADD19AFBC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5BCE2B7-7E94-4E88-8AEE-D4ADD19AFBC1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Frolikss.HabitTracker/HabitTracker/.DS_Store b/Frolikss.HabitTracker/HabitTracker/.DS_Store new file mode 100644 index 00000000..a1f5295f Binary files /dev/null and b/Frolikss.HabitTracker/HabitTracker/.DS_Store differ diff --git a/Frolikss.HabitTracker/HabitTracker/HabitTracker.csproj b/Frolikss.HabitTracker/HabitTracker/HabitTracker.csproj new file mode 100644 index 00000000..08a740d1 --- /dev/null +++ b/Frolikss.HabitTracker/HabitTracker/HabitTracker.csproj @@ -0,0 +1,14 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + diff --git a/Frolikss.HabitTracker/HabitTracker/Program.cs b/Frolikss.HabitTracker/HabitTracker/Program.cs new file mode 100644 index 00000000..b4a662eb --- /dev/null +++ b/Frolikss.HabitTracker/HabitTracker/Program.cs @@ -0,0 +1,353 @@ +using System.Globalization; +using DatabaseLogic; + +class Program +{ + private static DatabaseManager dbManager; + private static string connectionString = "Data Source = habit-tracker.db"; + private static string dateFormat = "dd.MM.yyyy"; + + static void Main() + { + var closeApp = false; + dbManager = new DatabaseManager(connectionString); + + ShowMainMenu(); + + while (!closeApp) + { + var input = Console.ReadLine(); + + switch (input) + { + case "a": + AddNewRecord(); + break; + case "u": + UpdateRecord(); + break; + case "d": + DeleteRecord(); + break; + case "v": + ShowRecords(); + break; + case "q": + closeApp = true; + break; + default: + Console.WriteLine("Incorrect input"); + break; + } + } + } + + static void ShowMainMenu() + { + Console.Clear(); + Console.WriteLine("--- Main Menu ---"); + Console.WriteLine("Select an option: "); + Console.WriteLine("a: add new record"); + Console.WriteLine("u: update record"); + Console.WriteLine("d: delete record"); + Console.WriteLine("v: view records"); + Console.WriteLine("q: quit"); + } + + static int GetHabitId() + { + Console.Clear(); + var habits = dbManager.GetAllHabits(); + var habitId = -1; + + var closeMenu = false; + + Console.WriteLine("Select an ID of existing habit or create a new one by entering 'n'"); + + if (habits.Count > 0) + { + ShowHabits(); + } + + while (!closeMenu) + { + var input = Console.ReadLine(); + + if (input == "n") + { + closeMenu = true; + + habitId = AddNewHabit(); + continue; + } + else if (habits.Count > 0 && + int.TryParse(input, out var inputId) && + habits.Any((habit) => habit.Id == inputId)) + { + closeMenu = true; + habitId = inputId; + } + else + { + Console.WriteLine("Incorrect input"); + } + } + + return habitId; + } + + static int AddNewHabit() + { + var habitId = -1; + + while (habitId <= 0) + { + Console.WriteLine("Write a habit name"); + var name = Console.ReadLine() ?? ""; + + Console.WriteLine("Write units of measurement"); + var units = Console.ReadLine() ?? ""; + + var habit = new Habit { Name = name, Units = units }; + habitId = dbManager.AddHabit(habit) ?? -1; + + if (habitId <= 0) + { + Console.WriteLine("Something went wrong, try creating a habit again"); + } + } + + + return habitId; + } + + static void ShowHabits() + { + var habits = dbManager.GetAllHabits(); + + Console.WriteLine( + $"{"ID",-5} {"Name",-10} {"Units",-12}" + ); + Console.WriteLine( + $"{new string('-', 5)} {new string('-', 10)} {new string('-', 12)}" + ); + + habits.ForEach((record) => + { + Console.WriteLine( + $"{record.Id,-5} {record.Name,-10} {record.Units,-12}" + ); + }); + } + + static void AddNewRecord() + { + var habitId = GetHabitId(); + + Console.Clear(); + var record = GetRecordFromUser(); + record.Habit.Id = habitId; + + dbManager.AddRecord(record); + Console.WriteLine("Record added successfully, enter any key to go back to main menu"); + Console.ReadLine(); + ShowMainMenu(); + } + + static void ShowRecords() + { + Console.Clear(); + var records = GetAndViewRecords(); + + if (records.Count == 0) + { + ShowMainMenu(); + return; + } + + Console.WriteLine("Press q to go back"); + + var closeMenu = false; + + while (!closeMenu) + { + var input = Console.ReadLine(); + + switch (input) + { + case "q": + closeMenu = true; + ShowMainMenu(); + break; + default: + Console.WriteLine("Incorrect input"); + break; + } + } + } + + static void UpdateRecord() + { + Console.Clear(); + var records = GetAndViewRecords(); + + if (records.Count == 0) + { + ShowMainMenu(); + return; + } + + Console.WriteLine("Select an ID of a record that you want to update"); + + var closeMenu = false; + + while (!closeMenu) + { + var input = Console.ReadLine(); + + if (int.TryParse(input, out var typedId) && records.Any((record) => record.Id == typedId)) + { + closeMenu = true; + + var habitId = GetHabitId(); + var record = GetRecordFromUser(); + + record.Id = typedId; + record.Habit.Id = habitId; + + dbManager.UpdateRecord(record); + Console.WriteLine("Record updated successfully, enter any key to go back to main menu"); + Console.ReadLine(); + ShowMainMenu(); + } + else + { + Console.WriteLine("Incorrect input"); + } + } + } + + static void DeleteRecord() + { + Console.Clear(); + var records = GetAndViewRecords(); + + if (records.Count == 0) + { + ShowMainMenu(); + return; + } + + Console.WriteLine("Select an ID of a record that you want to delete"); + + var closeMenu = false; + + while (!closeMenu) + { + var input = Console.ReadLine(); + + if (int.TryParse(input, out var typedId) && records.Any((record) => record.Id == typedId)) + { + closeMenu = true; + + dbManager.DeleteRecord(typedId); + Console.WriteLine("Record deleted successfully, enter any key to go back to main menu"); + Console.ReadLine(); + ShowMainMenu(); + } + else + { + Console.WriteLine("Incorrect input"); + } + } + } + + static List GetAndViewRecords() + { + var records = dbManager.GetAllRecords(); + + if (records.Count == 0) + { + Console.WriteLine("No records"); + return new List(); + } + + Console.WriteLine( + $"{"ID",-5} {"Quantity",-10} {"Date",-12} {"Habit",-15} {"Units",-10}" + ); + Console.WriteLine( + $"{new string('-', 5)} {new string('-', 10)} {new string('-', 12)} {new string('-', 15)} {new string('-', 10)}" + ); + + records.ForEach(record => + { + Console.WriteLine( + $"{record.Id,-5} {record.Quantity,-10} {record.Date?.ToString("dd.MM.yyyy"),-12} {record.Habit?.Name,-15} {record.Habit?.Units,-10}" + ); + }); + + return records; + } + + static Data GetRecordFromUser() + { + var record = new Data(); + + Console.WriteLine("Write a quantity of a habit"); + var quantity = GetQuantity(); + + Console.WriteLine($"Write a date of habit in format {dateFormat}"); + var date = GetDate(); + + record.Quantity = quantity; + record.Date = date; + return record; + } + + static DateTime? GetDate() + { + DateTime? date = null; + + while (date is null) + { + var userDate = Console.ReadLine(); + if (DateTime.TryParseExact( + userDate, + dateFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var parsedDate)) + { + date = parsedDate; + } + else + { + Console.WriteLine("Incorrect date"); + } + } + + + return date; + } + + static int GetQuantity() + { + var quantity = -1; + + while (quantity <= 0) + { + var userQuantity = Console.ReadLine(); + + if (int.TryParse(userQuantity, out var parsedQuantity) && parsedQuantity > 0) + { + quantity = parsedQuantity; + } + else + { + Console.WriteLine("Incorrect input"); + } + } + + return quantity; + } +} \ No newline at end of file