diff --git a/README.md b/README.md new file mode 100644 index 00000000..c2f17bd1 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +## Console Habit Tracker ## + +This is my first CRUD application! It tracks whatever the user wants to track! + +Made with C# and SQLite. + +## Requirements 🗒️ ## + +- Users need to be able to input the date of the occurrence of the habit + +- The application should store and retrieve data from a real database + +- When the application starts, it should create a sqlite database, if one isn’t present. + +- It should also create a table in the database, where the habit will be logged. + +- The users should be able to insert, delete, update and view their logged habit. + +- You should handle all possible errors so that the application never crashes. + +- You can only interact with the database using ADO.NET. You can’t use mappers such as Entity Framework or Dapper. + +## Features 🧶 ## + +- SQLite connection to store and read information. If the database does not exist, the program creates one on start. + +- Console based UI to navigate by user input +![alt text](image.png) + +- Users can Create, Read, Update or Delete entries :D + +## Challenges & Lessons 💽## + +- This was the first time I integrated a database to a program! I already knew SQL and some C#, but I've mostly worked with Python. It was fun to learn how to use a SQL connection on my program, and the C# documentation was really useful. + +- I've tried to organize my code with methods, but I only made it more complicated to read. So I tried to use the KISS and DRY methodology to keep it simple and make it easier to understand. + +## Areas to Improve 📔# + +- Make the tables more user-friendly when printed! + +- Organize the code better \ No newline at end of file diff --git a/image.png b/image.png new file mode 100644 index 00000000..57f6c816 Binary files /dev/null and b/image.png differ diff --git a/project/Program.cs b/project/Program.cs new file mode 100644 index 00000000..b97808b6 --- /dev/null +++ b/project/Program.cs @@ -0,0 +1,394 @@ +using System.IO; +using Microsoft.Data.Sqlite; +using SQLitePCL; + +void Main() { + bool play = true; + string idStr = ""; + int idHabit = 0; + string answer = ""; + + CreateDatabase(); + Console.Clear(); + + while (play) + { + switch (Options()) + { + case 1: // create + CreateInput(); + break; + case 2: // read + ReadTable(); + break; + case 3: // update + idStr = ""; + idHabit = 0; + + while(!int.TryParse(idStr, out idHabit)) + { + System.Console.WriteLine(); + System.Console.WriteLine("Input Habit ID to check: "); + idStr = Console.ReadLine(); + System.Console.WriteLine(); + } + CheckRow(idHabit); + System.Console.WriteLine(); + System.Console.WriteLine("Is that correct? (y/n)"); + answer = Console.ReadLine().Trim().ToLower(); + System.Console.WriteLine(); + if (answer == "y") + { + UpdateRow(idHabit); + System.Console.WriteLine(); + System.Console.WriteLine("Row updated!"); + System.Console.WriteLine("Press Enter to continue."); + Console.ReadKey(); + } + break; + case 4: // delete + idStr = ""; + idHabit = 0; + + while(!int.TryParse(idStr, out idHabit)) + { + System.Console.WriteLine(); + System.Console.WriteLine("Input Habit ID to check: "); + idStr = Console.ReadLine(); + System.Console.WriteLine(); + } + CheckRow(idHabit); + System.Console.WriteLine(); + System.Console.WriteLine("Is that correct? (y/n)"); + answer = Console.ReadLine().Trim().ToLower(); + System.Console.WriteLine(); + if (answer == "y") + { + DeleteRow(idHabit); + System.Console.WriteLine(); + System.Console.WriteLine("Row deleted!"); + System.Console.WriteLine("Press Enter to continue."); + Console.ReadKey(); + } + + break; + case 5: // exit + play = false; + break; + + } + System.Console.WriteLine(); + } + + System.Console.WriteLine("Exiting now. See you later!"); + System.Console.WriteLine(); + Environment.Exit(0); +} + +int Options() +{ + string userAnswer = ""; + int option = 0; + + System.Console.WriteLine("**** Options ****"); + System.Console.WriteLine("1) Create ........"); + System.Console.WriteLine("2) Read .........."); + System.Console.WriteLine("3) Update ........"); + System.Console.WriteLine("4) Delete ........"); + System.Console.WriteLine("5) Exit .........."); + System.Console.WriteLine(); + + while (!int.TryParse(userAnswer, out option) && option < 1 || option > 5) + { + userAnswer = Console.ReadLine(); + System.Console.WriteLine(); + } + + return option; +} + +void CreateDatabase() +{ + string connectionStr = $"Data Source=habits.db"; + + Batteries.Init(); + using var connection = new SqliteConnection(connectionStr); + connection.Open(); + + var createTable = @" + CREATE TABLE IF NOT EXISTS Habits( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Habit TEXT NOT NULL, + Quantity INTEGER NOT NULL, + Date TEXT + );"; + + using (var command = new SqliteCommand(createTable, connection)) + { + command.ExecuteNonQuery(); + } + + connection.Close(); +} + +void CreateRow(string habit, int quantity, DateOnly date) +{ + string connectionStr = $"Data Source=habits.db"; + + Batteries.Init(); + using var connection = new SqliteConnection(connectionStr); + connection.Open(); + + var createInput = $""" + INSERT OR IGNORE INTO Habits(Habit, Quantity, Date) + VALUES ('{habit}', '{quantity}', '{date.ToString()}'); + """; + + using (var command = new SqliteCommand(createInput, connection)) + { + command.ExecuteNonQuery(); + } + + connection.Close(); +} + +void CheckRow(int id) +{ + string connectionStr = $"Data Source=habits.db"; + + Batteries.Init(); + using var connection = new SqliteConnection(connectionStr); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = $""" + SELECT Id, + Habit, + Quantity, + Date + FROM Habits + WHERE Id = {id}; + """; + using var reader = command.ExecuteReader(); + + System.Console.WriteLine("** You Selected: **"); + while (reader.Read()) + { + var habitId = reader.GetString(0); + var habitName = reader.GetString(1); + var habitQuantity = reader.GetString(2); + var habitDate = reader.GetString(3); + + Console.WriteLine($"ID: {habitId} - HABIT: {habitName} - QUANTITY: {habitQuantity} - DATE: {habitDate}"); + } + + connection.Close(); +} + +void DeleteRow(int id) +{ + string connectionStr = $"Data Source=habits.db"; + + Batteries.Init(); + using var connection = new SqliteConnection(connectionStr); + connection.Open(); + + var createInput = $""" + DELETE FROM Habits + WHERE Id = {id}; + """; + + using (var command = new SqliteCommand(createInput, connection)) + { + command.ExecuteNonQuery(); + } + + connection.Close(); +} + +void UpdateRow(int id) +{ + string connectionStr = $"Data Source=habits.db"; + + Batteries.Init(); + using var connection = new SqliteConnection(connectionStr); + connection.Open(); + + string answer = ""; + int answerInt = 0; + string column = ""; + string value = ""; + + string quantityStr = ""; + int quantity = 0; + DateOnly date; + string dateStr = ""; + + + while (!int.TryParse(answer, out answerInt) || answerInt < 1 || answerInt > 3) + { + System.Console.WriteLine("Which value would you like to update?"); + System.Console.WriteLine("1) Habit name ...................."); + System.Console.WriteLine("2) Habit quantity ................"); + System.Console.WriteLine("3) Habit date ...................."); + System.Console.WriteLine(); + answer = Console.ReadLine(); + int.TryParse(answer, out answerInt); + + System.Console.WriteLine(); + } + + switch (answerInt) + { + case 1: + column = "Habit"; + System.Console.WriteLine("Input the new name"); + System.Console.WriteLine(); + value = Console.ReadLine(); + break; + case 2: + column = "Quantity"; + while (!int.TryParse(quantityStr, out quantity)){ + System.Console.WriteLine("Input the new quantity"); + System.Console.WriteLine(); + quantityStr = Console.ReadLine(); + } + value = quantity.ToString(); + break; + case 3: + column = "Date"; + while (!DateOnly.TryParse(dateStr, out date) || dateStr.ToLower().Trim() != "today" ){ + System.Console.WriteLine("Input the new date"); + System.Console.WriteLine(); + dateStr = Console.ReadLine(); + if (dateStr.ToLower().Trim() == "today") { + dateStr = DateTime.Today.ToString(); + break; + } + } + value = date.ToString(); + break; + } + + + var createInput = $""" + UPDATE Habits + SET {column} = {value} + WHERE Id = {id}; + """; + + using (var command = new SqliteCommand(createInput, connection)) + { + command.ExecuteNonQuery(); + } + + connection.Close(); +} + +void ReadTable() +{ + System.Console.WriteLine("** YOUR HABITS **"); + + using var connection = new SqliteConnection("Data Source=habits.db"); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = """ + SELECT Id, + Habit, + Quantity, + Date + FROM Habits + """; + using var reader = command.ExecuteReader(); + + while (reader.Read()) + { + var habitId = reader.GetString(0); + var habitName = reader.GetString(1); + var habitQuantity = reader.GetString(2); + var habitDate = reader.GetString(3); + + Console.WriteLine($"ID: {habitId} - HABIT: {habitName} - QUANTITY: {habitQuantity} - DATE: {habitDate}"); + } + + connection.Close(); + + System.Console.WriteLine(); + System.Console.WriteLine("Press Enter to continue."); + Console.ReadKey(); +} + + + +void CreateInput() +{ + int habitQuantity = 0; + DateOnly habitDate; + + System.Console.WriteLine("Habit Name:"); + string habitName = Console.ReadLine().Trim(); + System.Console.WriteLine(); + + System.Console.WriteLine("Quantity:"); + string quantityStr = Console.ReadLine(); + while (!int.TryParse(quantityStr, out habitQuantity)) + { + System.Console.WriteLine("Invalid quantity. Only integers are accepted!"); + System.Console.WriteLine("Quantity:"); + quantityStr = Console.ReadLine(); + } + System.Console.WriteLine(); + + System.Console.WriteLine("Date: (Tip: use 'Today' for today's date!)"); + string dateStr = Console.ReadLine().Trim().ToLower(); + if (dateStr != "today"){ + while (!DateOnly.TryParse(dateStr, out habitDate)) + { + System.Console.WriteLine(); + System.Console.WriteLine("Invalid date. Only dates are accepted!"); + System.Console.WriteLine("Date: (Tip: use 'Today' for today's date!)"); + dateStr = Console.ReadLine(); + } + } + else + { + habitDate = DateOnly.Parse(DateTime.Today.ToShortDateString()); + } + + System.Console.WriteLine(); + System.Console.WriteLine("** Your Input: **"); + System.Console.WriteLine($"Habit: {habitName}"); + System.Console.WriteLine($"Quantity: {habitQuantity}"); + System.Console.WriteLine($"Date: {habitDate}"); + System.Console.WriteLine(); + + System.Console.WriteLine("Is that correct? (y/n)"); + string userAnswer = Console.ReadLine().Trim().ToLower(); + System.Console.WriteLine(); + + if (userAnswer == "y") + { + System.Console.WriteLine("Saving habit......"); + CreateRow(habitName, habitQuantity, habitDate); + System.Console.WriteLine("Saved!"); + System.Console.WriteLine(); + System.Console.WriteLine("Press Enter to continue."); + Console.ReadKey(); + } + else if (userAnswer == "n") + { + System.Console.WriteLine("Habit input canceled!"); + } + else + { + while (userAnswer != "y" || userAnswer != "n") + { + System.Console.WriteLine("Please input 'y' for yes and 'n' for no."); + userAnswer = Console.ReadLine().Trim().ToLower(); + } + } + System.Console.WriteLine(); +} + +Main(); \ No newline at end of file diff --git a/project/habits.db b/project/habits.db new file mode 100644 index 00000000..d8b4dac7 Binary files /dev/null and b/project/habits.db differ diff --git a/project/project.csproj b/project/project.csproj new file mode 100644 index 00000000..f4d4be65 --- /dev/null +++ b/project/project.csproj @@ -0,0 +1,12 @@ + + + Exe + net9.0 + enable + enable + + + + + + \ No newline at end of file diff --git a/project/project.csproj.lscache b/project/project.csproj.lscache new file mode 100644 index 00000000..46144be8 --- /dev/null +++ b/project/project.csproj.lscache @@ -0,0 +1,248 @@ +version=1 + +# This file caches language service data to improve the performance of C# Dev Kit. +# It is not intended for manual editing. It can safely be deleted and will be +# regenerated automatically. For more information, see https://aka.ms/lscache +# +# To control where cache files are stored, use the following VS Code setting: +# "dotnet.projectsystem.cacheInProjectFolder": true + +[project] +language=C# +primary +lastDtbSucceeded + +[properties] +AssemblyName=project +CommandLineArgsForDesignTimeEvaluation=-langversion:13.0 -define:TRACE +CompilerGeneratedFilesOutputPath= +MaxSupportedLangVersion=13.0 +ProjectAssetsFile=obj/project.assets.json +RootNamespace=project +RunAnalyzers= +RunAnalyzersDuringLiveAnalysis= +SolutionPath=*Undefined* +TargetFrameworkIdentifier=.NETCoreApp +TargetPath=bin/Debug/net9.0/project.dll +TargetRefPath=obj/Debug/net9.0/ref/project.dll +TemporaryDependencyNodeTargetIdentifier=net9.0 + +[commandLineArguments] +/noconfig +/unsafe- +/checked- +/nowarn:1701,1702,1701,1702 +/fullpaths +/nostdlib+ +/errorreport:prompt +/warn:9 +/define:TRACE;DEBUG;NET;NET9_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER +/highentropyva+ +/nullable:enable +/debug+ +/debug:portable +/filealign:512 +/optimize- +/out:obj\Debug\net9.0\project.dll +/refout:obj\Debug\net9.0\refint\project.dll +/target:exe +/warnaserror- +/utf8output +/deterministic+ +/langversion:13.0 +/warnaserror+:NU1605,SYSLIB0011 + +[sourceFiles] +obj/Debug/net9.0/ + .NETCoreApp,Version=v9.0.AssemblyAttributes.cs + project.AssemblyInfo.cs + project.GlobalUsings.g.cs +Program.cs + +[metadataReferences] +/packs/Microsoft.NETCore.App.Ref/9.0.2/ref/net9.0/ + Microsoft.CSharp.dll + Microsoft.VisualBasic.Core.dll + Microsoft.VisualBasic.dll + Microsoft.Win32.Primitives.dll + Microsoft.Win32.Registry.dll + mscorlib.dll + netstandard.dll + System.AppContext.dll + System.Buffers.dll + System.Collections.Concurrent.dll + System.Collections.dll + System.Collections.Immutable.dll + System.Collections.NonGeneric.dll + System.Collections.Specialized.dll + System.ComponentModel.Annotations.dll + System.ComponentModel.DataAnnotations.dll + System.ComponentModel.dll + System.ComponentModel.EventBasedAsync.dll + System.ComponentModel.Primitives.dll + System.ComponentModel.TypeConverter.dll + System.Configuration.dll + System.Console.dll + System.Core.dll + System.Data.Common.dll + System.Data.DataSetExtensions.dll + System.Data.dll + System.Diagnostics.Contracts.dll + System.Diagnostics.Debug.dll + System.Diagnostics.DiagnosticSource.dll + System.Diagnostics.FileVersionInfo.dll + System.Diagnostics.Process.dll + System.Diagnostics.StackTrace.dll + System.Diagnostics.TextWriterTraceListener.dll + System.Diagnostics.Tools.dll + System.Diagnostics.TraceSource.dll + System.Diagnostics.Tracing.dll + System.dll + System.Drawing.dll + System.Drawing.Primitives.dll + System.Dynamic.Runtime.dll + System.Formats.Asn1.dll + System.Formats.Tar.dll + System.Globalization.Calendars.dll + System.Globalization.dll + System.Globalization.Extensions.dll + System.IO.Compression.Brotli.dll + System.IO.Compression.dll + System.IO.Compression.FileSystem.dll + System.IO.Compression.ZipFile.dll + System.IO.dll + System.IO.FileSystem.AccessControl.dll + System.IO.FileSystem.dll + System.IO.FileSystem.DriveInfo.dll + System.IO.FileSystem.Primitives.dll + System.IO.FileSystem.Watcher.dll + System.IO.IsolatedStorage.dll + System.IO.MemoryMappedFiles.dll + System.IO.Pipelines.dll + System.IO.Pipes.AccessControl.dll + System.IO.Pipes.dll + System.IO.UnmanagedMemoryStream.dll + System.Linq.dll + System.Linq.Expressions.dll + System.Linq.Parallel.dll + System.Linq.Queryable.dll + System.Memory.dll + System.Net.dll + System.Net.Http.dll + System.Net.Http.Json.dll + System.Net.HttpListener.dll + System.Net.Mail.dll + System.Net.NameResolution.dll + System.Net.NetworkInformation.dll + System.Net.Ping.dll + System.Net.Primitives.dll + System.Net.Quic.dll + System.Net.Requests.dll + System.Net.Security.dll + System.Net.ServicePoint.dll + System.Net.Sockets.dll + System.Net.WebClient.dll + System.Net.WebHeaderCollection.dll + System.Net.WebProxy.dll + System.Net.WebSockets.Client.dll + System.Net.WebSockets.dll + System.Numerics.dll + System.Numerics.Vectors.dll + System.ObjectModel.dll + System.Reflection.DispatchProxy.dll + System.Reflection.dll + System.Reflection.Emit.dll + System.Reflection.Emit.ILGeneration.dll + System.Reflection.Emit.Lightweight.dll + System.Reflection.Extensions.dll + System.Reflection.Metadata.dll + System.Reflection.Primitives.dll + System.Reflection.TypeExtensions.dll + System.Resources.Reader.dll + System.Resources.ResourceManager.dll + System.Resources.Writer.dll + System.Runtime.CompilerServices.Unsafe.dll + System.Runtime.CompilerServices.VisualC.dll + System.Runtime.dll + System.Runtime.Extensions.dll + System.Runtime.Handles.dll + System.Runtime.InteropServices.dll + System.Runtime.InteropServices.JavaScript.dll + System.Runtime.InteropServices.RuntimeInformation.dll + System.Runtime.Intrinsics.dll + System.Runtime.Loader.dll + System.Runtime.Numerics.dll + System.Runtime.Serialization.dll + System.Runtime.Serialization.Formatters.dll + System.Runtime.Serialization.Json.dll + System.Runtime.Serialization.Primitives.dll + System.Runtime.Serialization.Xml.dll + System.Security.AccessControl.dll + System.Security.Claims.dll + System.Security.Cryptography.Algorithms.dll + System.Security.Cryptography.Cng.dll + System.Security.Cryptography.Csp.dll + System.Security.Cryptography.dll + System.Security.Cryptography.Encoding.dll + System.Security.Cryptography.OpenSsl.dll + System.Security.Cryptography.Primitives.dll + System.Security.Cryptography.X509Certificates.dll + System.Security.dll + System.Security.Principal.dll + System.Security.Principal.Windows.dll + System.Security.SecureString.dll + System.ServiceModel.Web.dll + System.ServiceProcess.dll + System.Text.Encoding.CodePages.dll + System.Text.Encoding.dll + System.Text.Encoding.Extensions.dll + System.Text.Encodings.Web.dll + System.Text.Json.dll + System.Text.RegularExpressions.dll + System.Threading.Channels.dll + System.Threading.dll + System.Threading.Overlapped.dll + System.Threading.Tasks.Dataflow.dll + System.Threading.Tasks.dll + System.Threading.Tasks.Extensions.dll + System.Threading.Tasks.Parallel.dll + System.Threading.Thread.dll + System.Threading.ThreadPool.dll + System.Threading.Timer.dll + System.Transactions.dll + System.Transactions.Local.dll + System.ValueTuple.dll + System.Web.dll + System.Web.HttpUtility.dll + System.Windows.dll + System.Xml.dll + System.Xml.Linq.dll + System.Xml.ReaderWriter.dll + System.Xml.Serialization.dll + System.Xml.XDocument.dll + System.Xml.XmlDocument.dll + System.Xml.XmlSerializer.dll + System.Xml.XPath.dll + System.Xml.XPath.XDocument.dll + WindowsBase.dll +/ + microsoft.data.sqlite.core/10.0.8/lib/net8.0/Microsoft.Data.Sqlite.dll + sqlitepclraw.config.e_sqlite3/3.0.3/lib/net8.0/SQLitePCLRaw.batteries_v2.dll + sqlitepclraw.core/3.0.3/lib/net8.0/SQLitePCLRaw.core.dll + sqlitepclraw.provider.e_sqlite3/3.0.3/lib/net8.0/SQLitePCLRaw.provider.e_sqlite3.dll + +[analyzerReferences] +/packs/Microsoft.NETCore.App.Ref/9.0.2/analyzers/dotnet/cs/ + Microsoft.Interop.ComInterfaceGenerator.dll + Microsoft.Interop.JavaScript.JSImportGenerator.dll + Microsoft.Interop.LibraryImportGenerator.dll + Microsoft.Interop.SourceGeneration.dll + System.Text.Json.SourceGeneration.dll + System.Text.RegularExpressions.Generator.dll +/sdk/9.0.200/Sdks/Microsoft.NET.Sdk/analyzers/ + Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll + Microsoft.CodeAnalysis.NetAnalyzers.dll + +[analyzerConfigFiles] +/sdk/9.0.200/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_9_default.globalconfig +obj/Debug/net9.0/project.GeneratedMSBuildEditorConfig.editorconfig