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