diff --git a/Kerem.CodingTracker/Kerem.CodingTracker.Tests/Kerem.CodingTracker.Tests.csproj b/Kerem.CodingTracker/Kerem.CodingTracker.Tests/Kerem.CodingTracker.Tests.csproj
new file mode 100644
index 000000000..466c685eb
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker.Tests/Kerem.CodingTracker.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker.Tests/ValidatorTests.cs b/Kerem.CodingTracker/Kerem.CodingTracker.Tests/ValidatorTests.cs
new file mode 100644
index 000000000..72eda44d4
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker.Tests/ValidatorTests.cs
@@ -0,0 +1,79 @@
+using Kerem.CodingTracker.Features.CreateCodingSession;
+
+namespace Kerem.CodingTracker.Tests;
+
+public class ValidatorTests
+{
+ [Theory]
+ [InlineData("2026-08-24 14:30", true)]
+ [InlineData("2026-01-01 00:00", true)]
+ [InlineData("2026-8-24 14:30", false)]
+ [InlineData("2026-08-24 14:3", false)]
+ [InlineData("24-08-2026 14:30", false)]
+ [InlineData("2026-08-24", false)]
+ [InlineData("2026-08-24T14:30", false)]
+ [InlineData("", false)]
+ [InlineData("not a date", false)]
+ public void ValidateDateFormat_ReturnsExpectedResult(string input, bool expected)
+ {
+ var result = Validator.ValidateDateFormat(input);
+
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("abort", true)]
+ [InlineData("Abort", false)]
+ [InlineData("ABORT", false)]
+ [InlineData("", false)]
+ [InlineData("2026-08-24 14:30", false)]
+ public void Abort_ReturnsExpectedResult(string input, bool expected)
+ {
+ var result = Validator.Abort(input);
+
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void ValidateStartAndEndDate_ReturnsTrue_WhenStartIsBeforeEnd()
+ {
+ var start = new DateTime(2026, 8, 24, 9, 0, 0);
+ var end = new DateTime(2026, 8, 24, 17, 0, 0);
+
+ var result = Validator.ValidateStartAndEndDate(start, end);
+
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ValidateStartAndEndDate_ReturnsTrue_WhenStartEqualsEnd()
+ {
+ var same = new DateTime(2026, 8, 24, 9, 0, 0);
+
+ var result = Validator.ValidateStartAndEndDate(same, same);
+
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void ValidateStartAndEndDate_ReturnsFalse_WhenStartIsAfterEnd()
+ {
+ var start = new DateTime(2026, 8, 24, 17, 0, 0);
+ var end = new DateTime(2026, 8, 24, 9, 0, 0);
+
+ var result = Validator.ValidateStartAndEndDate(start, end);
+
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void ValidateStartAndEndDate_ReturnsFalse_WhenEndIsOnEarlierDay()
+ {
+ var start = new DateTime(2026, 8, 24, 9, 0, 0);
+ var end = new DateTime(2026, 8, 23, 9, 0, 0);
+
+ var result = Validator.ValidateStartAndEndDate(start, end);
+
+ Assert.False(result);
+ }
+}
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker.sln b/Kerem.CodingTracker/Kerem.CodingTracker.sln
new file mode 100644
index 000000000..1d723d49f
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker.sln
@@ -0,0 +1,23 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+#
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kerem.CodingTracker", "Kerem.CodingTracker\Kerem.CodingTracker.csproj", "{03B58CE3-2E2B-496D-BD1E-DD53FA74B4FE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kerem.CodingTracker.Tests", "Kerem.CodingTracker.Tests\Kerem.CodingTracker.Tests.csproj", "{E23F54F5-68A6-4329-97B5-5DCBB33F4594}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {03B58CE3-2E2B-496D-BD1E-DD53FA74B4FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03B58CE3-2E2B-496D-BD1E-DD53FA74B4FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03B58CE3-2E2B-496D-BD1E-DD53FA74B4FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03B58CE3-2E2B-496D-BD1E-DD53FA74B4FE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E23F54F5-68A6-4329-97B5-5DCBB33F4594}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E23F54F5-68A6-4329-97B5-5DCBB33F4594}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E23F54F5-68A6-4329-97B5-5DCBB33F4594}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E23F54F5-68A6-4329-97B5-5DCBB33F4594}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/DependencyInjection.cs b/Kerem.CodingTracker/Kerem.CodingTracker/DependencyInjection.cs
new file mode 100644
index 000000000..147f8a14d
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/DependencyInjection.cs
@@ -0,0 +1,26 @@
+using Kerem.CodingTracker.Domain.Interfaces;
+using Kerem.CodingTracker.Features.CreateCodingSession;
+using Kerem.CodingTracker.Features.DeleteCodingSession;
+using Kerem.CodingTracker.Features.EditCodingSession;
+using Kerem.CodingTracker.Features.FindAllCodingSession;
+using Kerem.CodingTracker.Infrastructure.Repositories;
+using Kerem.CodingTracker.UI;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Kerem.CodingTracker ;
+
+ public static class DependencyInjection
+ {
+ public static IServiceCollection AddApplication(this IServiceCollection services, string connectionString)
+ {
+ services.AddSingleton(new DapperDbContext(connectionString));
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ return services;
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Domain/Entities/CodingSession.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Domain/Entities/CodingSession.cs
new file mode 100644
index 000000000..bda93248c
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Domain/Entities/CodingSession.cs
@@ -0,0 +1,9 @@
+namespace Kerem.CodingTracker.Domain.Entities ;
+
+ public class CodingSession
+ {
+ public int Id { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public long Duration { get; set; }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Domain/Interfaces/ICodingSessionRepository.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Domain/Interfaces/ICodingSessionRepository.cs
new file mode 100644
index 000000000..84d3b573b
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Domain/Interfaces/ICodingSessionRepository.cs
@@ -0,0 +1,14 @@
+using Kerem.CodingTracker.Domain.Entities;
+
+namespace Kerem.CodingTracker.Domain.Interfaces ;
+
+ public interface ICodingSessionRepository
+ {
+ List ? FindAll();
+ void Create(CodingSession codingSession);
+ int CountCodingSessions();
+ CodingSession? FindById(int id);
+ void Save(CodingSession codingSession);
+
+ void Delete(int id);
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Features/CountCodingSession/CountCodingSession.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Features/CountCodingSession/CountCodingSession.cs
new file mode 100644
index 000000000..c2112ae84
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Features/CountCodingSession/CountCodingSession.cs
@@ -0,0 +1,20 @@
+using Kerem.CodingTracker.Domain.Interfaces;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Features.CreateCodingSession ;
+
+ public class CountCodingSession
+ {
+ private readonly ICodingSessionRepository _codingSessionRepository;
+
+ public CountCodingSession(ICodingSessionRepository codingSessionRepository)
+ {
+ _codingSessionRepository = codingSessionRepository;
+ }
+
+ public void CountCodingSessions()
+ {
+ var amount = _codingSessionRepository.CountCodingSessions();
+ AnsiConsole.MarkupLine($"[red]There are {amount} registered coding sessions in the database.[/]");
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Features/CreateCodingSession/CreateCodingSession.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Features/CreateCodingSession/CreateCodingSession.cs
new file mode 100644
index 000000000..cc3093f5f
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Features/CreateCodingSession/CreateCodingSession.cs
@@ -0,0 +1,91 @@
+using Kerem.CodingTracker.Domain.Entities;
+using Kerem.CodingTracker.Domain.Interfaces;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Features.CreateCodingSession ;
+
+ public class CreateCodingSession
+ {
+ private readonly ICodingSessionRepository _codingSessionRepository;
+
+ public CreateCodingSession(ICodingSessionRepository codingSessionRepository)
+ {
+ _codingSessionRepository = codingSessionRepository;
+ }
+
+ public void Create()
+ {
+ CodingSession codingSession = new CodingSession();
+ AnsiConsole.MarkupLine("[bold steelblue]Please enter the start date in the format of yyyy-mm-dd hh:mm[/]");
+ AnsiConsole.MarkupLine("[Orange1]Enter abort to exit back to the main menu[/]");
+
+ var startDate = Console.ReadLine() ?? " ";
+ bool shouldAbort = Validator.Abort(startDate);
+
+ if (shouldAbort)
+ {
+ AnsiConsole.MarkupLine("[Orange1]Aborted[/]");
+ return;
+ }
+
+
+ var emptyStartDate = string.IsNullOrEmpty(startDate);
+ if (emptyStartDate)
+ {
+ AnsiConsole.MarkupLine("[red]Date cannot be empty[/]");
+
+ return;
+ }
+
+
+ var correctFormat = Validator.ValidateDateFormat(startDate);
+
+ if (!correctFormat)
+ {
+ AnsiConsole.MarkupLine("[red]Format is invalid[/]");
+ return;
+ }
+
+ DateTime startTime = DateTime.Parse(startDate);
+ codingSession.StartTime = startTime;
+
+ AnsiConsole.MarkupLine("[bold steelblue]Please enter the end date in the format of yyyy-mm-dd hh:mm[/]");
+
+ var endDate = Console.ReadLine() ?? " ";
+
+ var emptyEndDate = string.IsNullOrEmpty(endDate);
+
+ if (emptyEndDate)
+ {
+ AnsiConsole.MarkupLine("[red]Date cannot be empty[/]");
+ return;
+ }
+
+ correctFormat = Validator.ValidateDateFormat(endDate);
+
+ if (!correctFormat)
+ {
+ AnsiConsole.MarkupLine("[red]Format is invalid[/]");
+ return;
+ }
+
+ DateTime endTime = DateTime.Parse(endDate);
+ codingSession.EndTime = endTime;
+
+ var correctDateTime = Validator.ValidateStartAndEndDate(codingSession.StartTime, codingSession.EndTime);
+
+ if (!correctDateTime)
+ {
+ AnsiConsole.MarkupLine("[red]Start date cannot be later than end date[/]");
+ return;
+ }
+
+
+ var difference = endTime - startTime;
+ var minutes = (int)difference.TotalMinutes;
+ codingSession.Duration = minutes;
+
+ _codingSessionRepository.Create(codingSession);
+ AnsiConsole.MarkupLine("[green]Coding session created[/]");
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Features/DeleteCodingSession/DeleteCodingSession.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Features/DeleteCodingSession/DeleteCodingSession.cs
new file mode 100644
index 000000000..af5024eef
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Features/DeleteCodingSession/DeleteCodingSession.cs
@@ -0,0 +1,33 @@
+using Kerem.CodingTracker.Domain.Interfaces;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Features.DeleteCodingSession ;
+
+ public class DeleteCodingSession
+ {
+ private readonly ICodingSessionRepository _codingSessionRepository;
+
+ public DeleteCodingSession(ICodingSessionRepository codingSessionRepository)
+ {
+ _codingSessionRepository = codingSessionRepository;
+ }
+ public void DeleteCodingSessionById()
+ {
+ AnsiConsole.MarkupLine("[blue]Please enter the id of the coding session you want to delete[/]");
+ int selectedSesssion = int.TryParse(Console.ReadLine(), out var selectedId) ? selectedId : 0;
+ if (selectedSesssion == 0)
+ {
+ AnsiConsole.MarkupLine("[red]The input its not a numerical value[/]");
+ return;
+ }
+ var codingSession = _codingSessionRepository.FindById(selectedSesssion);
+ if (codingSession == null)
+ {
+ AnsiConsole.MarkupLine("[red]The coding session you want to delete does not exist[/]");
+ return;
+ }
+ _codingSessionRepository.Delete(codingSession.Id);
+ AnsiConsole.MarkupLine($"[green]Coding session with id {codingSession.Id} has been deleted[/]");
+
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Features/EditCodingSession/EditCodingSession.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Features/EditCodingSession/EditCodingSession.cs
new file mode 100644
index 000000000..a3988fe3f
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Features/EditCodingSession/EditCodingSession.cs
@@ -0,0 +1,97 @@
+using Kerem.CodingTracker.Domain.Entities;
+using Kerem.CodingTracker.Domain.Interfaces;
+using Kerem.CodingTracker.Features.CreateCodingSession;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Features.EditCodingSession ;
+
+ public class EditCodingSession
+ {
+ private readonly ICodingSessionRepository _codingSessionRepository;
+
+ public EditCodingSession(ICodingSessionRepository codingSessionRepository)
+ {
+ _codingSessionRepository = codingSessionRepository;
+ }
+
+ public void CodingSessionEdit()
+ {
+ AnsiConsole.MarkupLine("[blue]Please enter the id of the coding session you want to edit[/]");
+ int id = int.TryParse(Console.ReadLine(), out var selectedId) ? selectedId : 0;
+ if (id == 0)
+ {
+ AnsiConsole.MarkupLine("[red]The input its not a numerical value[/]");
+ return;
+ }
+ var codingSession = _codingSessionRepository.FindById(id);
+ if (codingSession == null)
+ {
+ AnsiConsole.MarkupLine("[red]The coding session you want to edit does not exist[/]");
+ return;
+ }
+
+ bool runProgram = true;
+ while (runProgram)
+ {
+ AnsiConsole.MarkupLine("[blue]Please select 1 to edit start date, 2 to edit end date or 3 to exit[/]");
+ int selectedProperty = int.TryParse(Console.ReadLine(), out var selected) ? selected : 0;
+ switch (selectedProperty)
+ {
+ case 0:
+ AnsiConsole.MarkupLine("The selected choice was a incorrect value");
+ break;
+ case 1:
+ AnsiConsole.MarkupLine("[bold steelblue]Please enter the start date in the format of yyyy-mm-dd hh:mm[/]");
+ var startDate = Console.ReadLine() ?? " ";
+ var emptyStartDate = string.IsNullOrEmpty(startDate);
+ if (emptyStartDate)
+ {
+ AnsiConsole.MarkupLine("[red]Date cannot be empty[/]");
+
+ return;
+ }
+
+ var correctFormat = Validator.ValidateDateFormat(startDate);
+
+ if (!correctFormat)
+ {
+ AnsiConsole.MarkupLine("[red]Format is invalid[/]");
+ return;
+ }
+ DateTime startTime = DateTime.Parse(startDate);
+ codingSession.StartTime = startTime;
+ break;
+ case 2:
+ AnsiConsole.MarkupLine("[blue]Please enter the end date in the format of yyyy-mm-dd hh:mm[/]");
+ var endDate = Console.ReadLine() ?? " ";
+ var emptyEndDate = string.IsNullOrEmpty(endDate);
+ if (emptyEndDate)
+ {
+ AnsiConsole.MarkupLine("[red]Date cannot be empty[/]");
+
+ return;
+ }
+ var correctFormatEndDate = Validator.ValidateDateFormat(endDate);
+
+ if (!correctFormatEndDate)
+ {
+ AnsiConsole.MarkupLine("[red]Format is invalid[/]");
+ return;
+ }
+ DateTime endTime = DateTime.Parse(endDate);
+ codingSession.EndTime = endTime;
+ break;
+ case 3:
+ runProgram = false;
+ break;
+ }
+ }
+
+ var difference = codingSession.EndTime - codingSession.StartTime;
+ var minutes = (int)difference.TotalMinutes;
+ codingSession.Duration = minutes;
+ _codingSessionRepository.Save(codingSession);
+ AnsiConsole.MarkupLine("[green]Coding session has been updated[/]");
+
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Features/FindAllCodingSession/FindAllCodingSession.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Features/FindAllCodingSession/FindAllCodingSession.cs
new file mode 100644
index 000000000..41264f608
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Features/FindAllCodingSession/FindAllCodingSession.cs
@@ -0,0 +1,41 @@
+using Kerem.CodingTracker.Domain.Interfaces;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Features.FindAllCodingSession ;
+
+ public class FindAllCodingSession
+ {
+ private readonly ICodingSessionRepository _codingSessionRepository;
+
+ public FindAllCodingSession(ICodingSessionRepository codingSessionRepository)
+ {
+ _codingSessionRepository = codingSessionRepository;
+ }
+
+ public void FindAll()
+ {
+ var amount = _codingSessionRepository.FindAll();
+
+ if (amount == null)
+ {
+ AnsiConsole.MarkupLine("[red]There are no registered coding sessions in the database.[/]");
+ return;
+ }
+
+ var table = new Table()
+ .RoundedBorder()
+ .BorderColor(Color.Green);
+
+ table.AddColumn(("Id"));
+ table.AddColumn(("Start Time"));
+ table.AddColumn(("End Time"));
+ table.AddColumn(("Duration"));
+
+ foreach (var codingSession in amount)
+ {
+ table.AddRow($"{codingSession.Id}", $"{codingSession.StartTime}", $"{codingSession.EndTime}", $"{codingSession.Duration}");
+
+ }
+ AnsiConsole.Write(table);
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Persistance/DapperDbContext.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Persistance/DapperDbContext.cs
new file mode 100644
index 000000000..5ea36f6ba
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Persistance/DapperDbContext.cs
@@ -0,0 +1,34 @@
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+namespace Kerem.CodingTracker ;
+
+ public class DapperDbContext : IDisposable
+ {
+ private readonly string _connectionString;
+ private IDbConnection _connection;
+
+ public DapperDbContext(string connectionString)
+ {
+ _connectionString = connectionString;
+ }
+
+ public IDbConnection GetConnection()
+ {
+ if (_connection == null || _connection.State != ConnectionState.Open)
+ {
+ _connection = new SqlConnection(_connectionString);
+ _connection.Open();
+ }
+ return _connection;
+ }
+
+ public void Dispose()
+ {
+ if (_connection != null && _connection.State == ConnectionState.Open)
+ {
+ _connection.Close();
+ _connection.Dispose();
+ }
+ }
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Repositories/CodingSessionRepository.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Repositories/CodingSessionRepository.cs
new file mode 100644
index 000000000..916a3a115
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Repositories/CodingSessionRepository.cs
@@ -0,0 +1,61 @@
+using Dapper;
+using Kerem.CodingTracker.Domain.Entities;
+using Kerem.CodingTracker.Domain.Interfaces;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Infrastructure.Repositories ;
+
+ public class CodingSessionRepository : ICodingSessionRepository
+ {
+ private readonly DapperDbContext _dapperDbContext;
+
+ public CodingSessionRepository(DapperDbContext context)
+ {
+ _dapperDbContext = context;
+ }
+ public List FindAll()
+ {
+ var connection = _dapperDbContext.GetConnection();
+ var sql = "SELECT * FROM CodingSession";
+ var codingSessions = connection.Query(sql).ToList();
+ return codingSessions;
+ }
+
+ public void Create(CodingSession codingSession)
+ {
+ var connection = _dapperDbContext.GetConnection();
+ var sql = "INSERT INTO CodingSession (startTime, endTime, duration) VALUES (@StartTime, @EndTime, @Duration)";
+ connection.Execute(sql, codingSession);
+ }
+
+ public int CountCodingSessions()
+ {
+ var connection = _dapperDbContext.GetConnection();
+ var sql = "SELECT COUNT(*) FROM CodingSession";
+ return connection.ExecuteScalar(sql);
+ }
+
+ public CodingSession FindById(int id)
+ {
+ var connection = _dapperDbContext.GetConnection();
+ var sql = "SELECT * FROM CodingSession WHERE Id = @Id";
+ var codingSession = connection.Query(sql, new { Id = id }).FirstOrDefault();
+ return codingSession;
+ }
+
+ public void Save(CodingSession codingSession)
+ {
+ var connection = _dapperDbContext.GetConnection();
+ var sql = $"UPDATE CodingSession SET startTime = @StartTime, endTime = @EndTime, duration = @Duration WHERE Id = @Id";
+ connection.ExecuteScalar(sql, new { codingSession.StartTime, codingSession.EndTime, codingSession.Duration, codingSession.Id });
+ }
+
+ public void Delete(int id)
+ {
+ var connection = _dapperDbContext.GetConnection();
+ var sql = $"DELETE FROM CodingSession WHERE Id = @Id";
+ connection.ExecuteScalar(sql, new { Id = id });
+ }
+
+
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Utils/Validator.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Utils/Validator.cs
new file mode 100644
index 000000000..0bf02a70f
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Infrastructure/Utils/Validator.cs
@@ -0,0 +1,36 @@
+using System.Text.RegularExpressions;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.Features.CreateCodingSession ;
+
+ public static class Validator
+ {
+ public static bool ValidateDateFormat(String date)
+ {
+ string pattern = @"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]) ([01]\d|2[0-3]):[0-5]\d$";
+ if (Regex.IsMatch(date, pattern))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public static bool Abort(string choice)
+ {
+ if (choice == "abort")
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public static bool ValidateStartAndEndDate(DateTime startDate, DateTime endDate)
+ {
+ if (startDate > endDate)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ }
\ No newline at end of file
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Kerem.CodingTracker.csproj b/Kerem.CodingTracker/Kerem.CodingTracker/Kerem.CodingTracker.csproj
new file mode 100644
index 000000000..2e54ea823
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Kerem.CodingTracker.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/Program.cs b/Kerem.CodingTracker/Kerem.CodingTracker/Program.cs
new file mode 100644
index 000000000..0f38f353b
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/Program.cs
@@ -0,0 +1,16 @@
+using Kerem.CodingTracker;
+using Kerem.CodingTracker.UI;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+var builder = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: false);
+IConfiguration config = builder.Build();
+var connectionString = config.GetConnectionString("DefaultConnection");
+
+var serviceProvider = new ServiceCollection()
+ .AddApplication(connectionString)
+ .BuildServiceProvider();
+
+serviceProvider.GetRequiredService().Menu();
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/UI/ConsoleMenu.cs b/Kerem.CodingTracker/Kerem.CodingTracker/UI/ConsoleMenu.cs
new file mode 100644
index 000000000..5c52651b4
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/UI/ConsoleMenu.cs
@@ -0,0 +1,66 @@
+using Kerem.CodingTracker.Features;
+using Kerem.CodingTracker.Features.CreateCodingSession;
+using Kerem.CodingTracker.Features.DeleteCodingSession;
+using Kerem.CodingTracker.Features.EditCodingSession;
+using Kerem.CodingTracker.Features.FindAllCodingSession;
+using Spectre.Console;
+
+namespace Kerem.CodingTracker.UI ;
+
+ public class ConsoleMenu
+ {
+ private readonly CreateCodingSession _createCodingSession;
+ private readonly CountCodingSession _countCodingSession;
+ private readonly FindAllCodingSession _findAllCodingSession;
+ private readonly EditCodingSession _editCodingSession;
+ private readonly DeleteCodingSession _deleteCodingSession;
+
+ public ConsoleMenu(CreateCodingSession createCodingSession, CountCodingSession countCodingSession, FindAllCodingSession findAllCodingSession, EditCodingSession editCodingSession, DeleteCodingSession deleteCodingSession)
+ {
+ _createCodingSession = createCodingSession;
+ _countCodingSession = countCodingSession;
+ _findAllCodingSession = findAllCodingSession;
+ _editCodingSession = editCodingSession;
+ _deleteCodingSession = deleteCodingSession;
+ }
+
+ public void Menu()
+ {
+ AnsiConsole.Write(new FigletText("Coding Tracker").Color(Color.SteelBlue));
+ while (true)
+ {
+ string choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Please select an [bold steelblue]option[/]:")
+ .AddChoices(
+ "1. View all coding sessions",
+ "2. Create a coding session",
+ "3. Edit a coding session",
+ "4. Delete a coding session",
+ "5. Exit"));
+
+ switch (choice[0])
+ {
+ case '1':
+ _countCodingSession.CountCodingSessions();
+ _findAllCodingSession.FindAll();
+ break;
+ case '2':
+ _createCodingSession.Create();
+ break;
+ case '3':
+ _findAllCodingSession.FindAll();
+ _editCodingSession.CodingSessionEdit();
+ break;
+ case '4':
+ _findAllCodingSession.FindAll();
+ _deleteCodingSession.DeleteCodingSessionById();
+ break;
+ case '5':
+ AnsiConsole.MarkupLine("[bold green]Goodbye![/]");
+ return;
+ }
+ }
+ }
+ }
+
diff --git a/Kerem.CodingTracker/Kerem.CodingTracker/appsettings.json b/Kerem.CodingTracker/Kerem.CodingTracker/appsettings.json
new file mode 100644
index 000000000..3097bcae1
--- /dev/null
+++ b/Kerem.CodingTracker/Kerem.CodingTracker/appsettings.json
@@ -0,0 +1,12 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "System": "Error"
+ }
+ },
+ "ConnectionStrings": {
+ "DefaultConnection": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=CodingTracker;Integrated Security=true;TrustServerCertificate=true;"
+ }
+}
\ No newline at end of file
diff --git a/Kerem.CodingTracker/NuGet.config b/Kerem.CodingTracker/NuGet.config
new file mode 100644
index 000000000..765346e53
--- /dev/null
+++ b/Kerem.CodingTracker/NuGet.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Kerem.CodingTracker/README.md b/Kerem.CodingTracker/README.md
new file mode 100644
index 000000000..faaee4f21
--- /dev/null
+++ b/Kerem.CodingTracker/README.md
@@ -0,0 +1,121 @@
+# Coding Tracker
+
+A console app for logging coding sessions, start time, end time, and an automatically
+calculated duration, built as a follow-up to the Habit Logger project. This time the
+focus is on handling dates/times correctly, using an external library (Dapper +
+Spectre.Console), and applying Separation of Concerns instead of one flat `Program.cs`.
+
+## Features
+
+- Log a coding session by entering a start and end date/time
+- View all logged sessions in a formatted table (Spectre.Console)
+- Edit a session's start or end time
+- Delete a session
+- Duration is always derived from `EndTime - StartTime`, never entered by hand
+
+## Tech stack
+
+- .NET 8 / C#
+- [Dapper](https://github.com/DapperLib/Dapper) for data access (no raw ADO.NET, no EF)
+- [Spectre.Console](https://spectreconsole.net/) for all console output (tables, prompts, styled text)
+- Microsoft.Extensions.DependencyInjection + Microsoft.Extensions.Configuration for DI and config
+- SQL Server (via `Microsoft.Data.SqlClient`) as the backing store
+
+## Project structure
+
+The project is organized by architectural layer, with features further split into their
+own folders so each operation (create/edit/delete/list/count) lives in its own file:
+
+```
+Kerem.CodingTracker/
+ Domain/
+ Entities/CodingSession.cs # the CodingSession model (Id, StartTime, EndTime, Duration)
+ Interfaces/ICodingSessionRepository.cs
+ Infrastructure/
+ Persistance/DapperDbContext.cs # wraps the SqlConnection used by Dapper
+ Repositories/CodingSessionRepository.cs
+ Utils/Validator.cs # date format / abort / start-before-end checks
+ Features/
+ CreateCodingSession/
+ EditCodingSession/
+ DeleteCodingSession/
+ FindAllCodingSession/
+ CountCodingSession/
+ UI/ConsoleMenu.cs # the main menu loop
+ DependencyInjection.cs # wires everything up via IServiceCollection
+ Program.cs # entry point: builds config, builds DI, runs the menu
+ appsettings.json # connection string lives here, not hardcoded
+
+Kerem.CodingTracker.Tests/
+ ValidatorTests.cs # unit tests for the pure validation methods
+```
+
+Each feature only depends on `ICodingSessionRepository`, never on the concrete
+repository or Dapper directly, so the console/business logic stays decoupled from
+the data access layer.
+
+## Why these design choices
+
+- **Feature folders over one big file**: each user action (create, edit, delete, list,
+ count) is its own class with a single public method, injected with only the
+ repository it needs. This maps directly to the Separation of Concerns requirement
+ and makes it obvious where to look when a specific menu option misbehaves.
+- **`ICodingSessionRepository` interface**: the features depend on the interface, not
+ `CodingSessionRepository` directly, so the data access implementation could be swapped
+ (e.g. for a different database or a mock in tests) without touching feature code.
+- **`Validator` as a static utility class**: date-format checking, the "abort" keyword
+ check, and the start-before-end check are pure functions with no console or database
+ dependency, so they're cheap to unit test in isolation (see Testing below) and reused
+ across `CreateCodingSession` and `EditCodingSession` instead of being duplicated.
+- **Duration is never user input**: `CodingSession.Duration` is only ever set from
+ `(EndTime - StartTime).TotalMinutes`, calculated in the feature classes after both
+ dates have been validated.
+- **Config over hardcoding**: the connection string lives in `appsettings.json` and is
+ read once in `Program.cs`, then passed into `DependencyInjection.AddApplication`,
+ so nothing in the data layer needs to know where the config file lives.
+
+## Date/time format
+
+The app only accepts dates typed in exactly this format:
+
+```
+yyyy-MM-dd HH:mm
+```
+
+Example: `2026-08-24 14:30`
+
+Anything else (wrong separators, missing leading zeros, a date with no time, etc.) is
+rejected by `Validator.ValidateDateFormat` before it's parsed, and the end date is
+checked against the start date (`Validator.ValidateStartAndEndDate`) so a session can't
+end before it starts.
+
+## Getting started
+
+1. Make sure you have the .NET 8 SDK installed.
+2. Update the `ConnectionStrings:DefaultConnection` value in
+ `Kerem.CodingTracker/appsettings.json` to point at a SQL Server instance you have
+ access to (default is `localhost\SQLEXPRESS` with integrated security).
+3. Create the `CodingSession` table on that database:
+
+ ```sql
+ CREATE TABLE CodingSession (
+ Id INT IDENTITY(1,1) PRIMARY KEY,
+ StartTime DATETIME NOT NULL,
+ EndTime DATETIME NOT NULL,
+ Duration DECIMAL(10, 2) NOT NULL
+ );
+ ```
+
+4. Run the app from the `Kerem.CodingTracker` folder:
+
+ ```
+ dotnet run --project Kerem.CodingTracker
+ ```
+
+## Running the tests
+
+```
+dotnet test
+```
+
+`Kerem.CodingTracker.Tests` covers the `Validator` methods
diff --git a/Kerem.CodingTracker/global.json b/Kerem.CodingTracker/global.json
new file mode 100644
index 000000000..2ddda36c2
--- /dev/null
+++ b/Kerem.CodingTracker/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "8.0.0",
+ "rollForward": "latestMinor",
+ "allowPrerelease": false
+ }
+}
\ No newline at end of file