diff --git a/.gitignore b/.gitignore index 0b26c11d..e1293c09 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ *.user *.userosscache *.sln.docstates +*.db # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/CodeReviews.Console.HabitTracker.sln b/CodeReviews.Console.HabitTracker.sln new file mode 100644 index 00000000..fab62861 --- /dev/null +++ b/CodeReviews.Console.HabitTracker.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HabitTracker.natholder", "HabitTracker.natholder\HabitTracker.natholder.csproj", "{AA4088A3-C32F-4B29-48E0-339A2F9967C6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA4088A3-C32F-4B29-48E0-339A2F9967C6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D67F1646-947A-4847-A048-583B3565ABC1} + EndGlobalSection +EndGlobal diff --git a/HabitTracker.natholder/HabitTracker.natholder.csproj b/HabitTracker.natholder/HabitTracker.natholder.csproj new file mode 100644 index 00000000..6ed0cec0 --- /dev/null +++ b/HabitTracker.natholder/HabitTracker.natholder.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/HabitTracker.natholder/Program.cs b/HabitTracker.natholder/Program.cs new file mode 100644 index 00000000..d4d9bd15 --- /dev/null +++ b/HabitTracker.natholder/Program.cs @@ -0,0 +1,269 @@ +using Microsoft.Data.Sqlite; + +namespace HabitTracker +{ + + public class Runs + { + public int Id { get; set; } + public DateTime Date { get; set; } + public double Miles { get; set; } + } + class Program + { + static string dbFileName = "HabitTracker.db"; + static string connectionString = $"Data Source ={dbFileName}"; + static void Main(string[] args) + { + InitDB(); + MenuLoop(); + } + + static void InitDB() + { + try + { + using var connection = new SqliteConnection(connectionString); + connection.Open(); + Console.WriteLine($"Database {dbFileName} created or opened successfully."); + CreateTable(connection); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + } + } + static void MenuLoop() + { + string menuText = @" + __ ____ __ ______ __ + / |/ (_) /__ _____ /_ __/________ ______/ /_____ _____ + / /|_/ / / / _ \/ ___/ / / / ___/ __ `/ ___/ //_/ _ \/ ___/ + / / / / / / __(__ ) / / / / / /_/ / /__/ ,< / __/ / +/_/ /_/_/_/\___/____/ /_/ /_/ \__,_/\___/_/|_|\___/_/"; + bool running = true; + string? userInput; + int number; + Console.WriteLine(menuText); + while (running) + { + ShowMenu(); + userInput = Console.ReadLine(); + if (int.TryParse(userInput, out number) && number > 0 && number <= 6) + { + switch (userInput) + { + case "1": + SelectRecords(); + break; + case "2": + Insert(); + break; + case "3": + Update(); + break; + case "4": + Delete(); + break; + default: + running = false; + break; + } + } + else + { + Console.WriteLine("Invalid input. Please try again."); + } + } + } + static void CreateTable(SqliteConnection connection) + { + string CreateTableQuery = @" + CREATE TABLE IF NOT EXISTS Habits ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Date DATETIME, + Miles DOUBLE + )"; + + using var command = new SqliteCommand(CreateTableQuery, connection); + command.ExecuteNonQuery(); + Console.WriteLine("Table 'Habits' created or already exists."); + } + + static void Insert() + { + DateTime date = GetDate(); + double miles = GetMiles(); + using var connection = new SqliteConnection(connectionString); + connection.Open(); + var insert = connection.CreateCommand(); + insert.CommandText = $"INSERT INTO Habits (Date, Miles) VALUES ('{date}', {miles})"; + insert.ExecuteNonQuery(); + connection.Close(); + } + + static void SelectRecords() + { + Console.Clear(); + using var connection = new SqliteConnection(connectionString); + connection.Open(); + var select = connection.CreateCommand(); + select.CommandText = $"SELECT * FROM Habits"; + List tableData = []; + SqliteDataReader reader = select.ExecuteReader(); + if (reader.HasRows) + { + while (reader.Read()) + { + tableData.Add( + new Runs + { + Id = reader.GetInt32(0), + Date = reader.GetDateTime(1), + Miles = reader.GetDouble(2) + } + ); + } + } + else + { + Console.WriteLine("No rows found."); + } + connection.Close(); + + Console.WriteLine("---------------------------------------"); + Console.WriteLine(" Id | Date | Miles"); + Console.WriteLine("---------------------------------------"); + foreach (var row in tableData) + { + Console.WriteLine($"| {row.Id} | {row.Date.ToString("MM-dd-yyyy")} | {row.Miles} |"); + } + Console.WriteLine("---------------------------------------"); + + } + + static void Delete() + { + int id = GetId(); + using var connection = new SqliteConnection(connectionString); + connection.Open(); + var insert = connection.CreateCommand(); + insert.CommandText = $"DELETE FROM Habits WHERE Id = {id}"; + int rowsAffected = insert.ExecuteNonQuery(); + + if (rowsAffected > 0) + { + Console.WriteLine($"Successfully deleted record with ID: {id}"); + } + else + { + Console.WriteLine($"No record found with ID: {id}"); + } + + connection.Close(); + } + + static void Update() + { + Console.Clear(); + SelectRecords(); + + Console.WriteLine("What is the id of the record you want to update?"); + int id = GetId(); + + Console.WriteLine("Enter new values for this record:"); + DateTime newDate = GetDate(); + double newMiles = GetMiles(); + + using var connection = new SqliteConnection(connectionString); + connection.Open(); + var update = connection.CreateCommand(); + update.CommandText = $"UPDATE Habits SET Date = @date, Miles = @miles WHERE Id = {id}"; + update.Parameters.AddWithValue("@date", newDate); + update.Parameters.AddWithValue("@miles", newMiles); + int rowsAffected = update.ExecuteNonQuery(); + + if (rowsAffected > 0) + { + Console.WriteLine($"Successfully updated record with ID: {id}"); + } + else + { + Console.WriteLine($"No record found with ID: {id}"); + } + + connection.Close(); + } + + + static void ShowMenu() + { + Console.WriteLine("1. View"); + Console.WriteLine("2. Insert"); + Console.WriteLine("3. Update"); + Console.WriteLine("4. Delete"); + Console.WriteLine("5. Quit"); + } + + static double GetMiles() + { + double miles; + string? input; + Console.WriteLine("How many miles did you run?"); + input = Console.ReadLine(); + if (double.TryParse(input, out miles)) + { + return miles; + } + else + { + Console.WriteLine("Please enter a valid number"); + return GetMiles(); + } + } + + static int GetId() + { + int id; + string? input; + Console.WriteLine("Enter the id of the record you would like to delete."); + input = Console.ReadLine(); + if (int.TryParse(input, out id)) + { + return id; + } + else + { + Console.WriteLine("Please enter a valid number"); + return GetId(); + } + } + + static DateTime GetDate() + { + string? input; + DateTime date; + Console.WriteLine("What day was your run? (type 'td' for today's date, or enter date as MM/DD/YYYY):"); + input = Console.ReadLine(); + + if (string.IsNullOrWhiteSpace(input)) + { + Console.WriteLine("Please enter a valid date or 'td' for today."); + return GetDate(); // Recursive call for empty input + } + + if (input?.ToLower() == "td") + { + return DateTime.Today; + } + + if (DateTime.TryParse(input, out date)) + { + return date; + } + + Console.WriteLine("Invalid date format. Please try again (e.g., 02/16/2026) or type 'td' for today."); + return GetDate(); // Recursive call for invalid date + } + } +} diff --git a/HabitTracker.natholder/readme.md b/HabitTracker.natholder/readme.md new file mode 100644 index 00000000..e01a100c --- /dev/null +++ b/HabitTracker.natholder/readme.md @@ -0,0 +1,50 @@ +# HabitTracker Console App + +A console application to log occurrences of miles (runs). The app stores entries in a SQLite database using ADO.NET and supports inserting, viewing, updating and deleting records. + +--- + +## Quick overview + +- Record format: `Date` (when the habit occurred) and `Miles` (quantity). +- Database: `HabitTracker.db` is created automatically when the app runs. +- Storage access: ADO.NET only (no EF / Dapper). Parameterized SQL is used to prevent injection. + +--- + +## How to run + +1. Open a terminal in the project folder `HabitTracker.natholder`. +2. Build & run: + + ```bash + dotnet run + ``` + +3. The app will create `HabitTracker.db` (if missing) and the `Habits` table. +4. Use the menu to Insert, View, Update, or Delete records. + +--- + +## Features + +- Creates a sqlite db and habit table on first run or if they have been deleted. +- Insert records with validated values.. +- View (select) all records +- Update an existing record +- Delete a record +- input validation and error handling + +--- + +## Thought process / retrospective + +I think that once i got started and made a little bit of progress, that the rest of the project came a lot easier. I had some trouble implementing the view command and had to use the video tutorial. I feel like I am learning though, and am continuting to improve. + +--- + +## Potential improvements (next steps) + +- Add unit tests for parsing/validation logic. + +---