diff --git a/CodingTracker.Tests/CodingTracker.Tests.csproj b/CodingTracker.Tests/CodingTracker.Tests.csproj
new file mode 100644
index 0000000..171f487
--- /dev/null
+++ b/CodingTracker.Tests/CodingTracker.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CodingTracker.Tests/ControllerTests/TrackerControllerTests.cs b/CodingTracker.Tests/ControllerTests/TrackerControllerTests.cs
new file mode 100644
index 0000000..45a80bc
--- /dev/null
+++ b/CodingTracker.Tests/ControllerTests/TrackerControllerTests.cs
@@ -0,0 +1,31 @@
+using CodingTracker.kilozdazolik.Controllers;
+using CodingTracker.kilozdazolik.Models;
+using CodingTracker.kilozdazolik.Services;
+using FakeItEasy;
+
+namespace CodingTracker.Tests.ControllerTests;
+
+public class TrackerControllerTests
+{
+ private readonly ITrackerService _service;
+ private readonly TrackerController _controller;
+
+ public TrackerControllerTests()
+ {
+ _service = A.Fake();
+ _controller = new TrackerController(_service);
+ }
+
+ [Fact]
+ public void ViewAllSessions_CallsServiceToGetAllSessions()
+ {
+ //Arrange
+ A.CallTo(() => _service.GetAllSession()).Returns(new List());
+
+ //Act
+ _controller.ViewAllSessions();
+
+ //Assert
+ A.CallTo(() => _service.GetAllSession()).MustHaveHappenedOnceExactly();
+ }
+}
\ No newline at end of file
diff --git a/CodingTracker.Tests/HelperTests/HelperTests.cs b/CodingTracker.Tests/HelperTests/HelperTests.cs
new file mode 100644
index 0000000..4ba7664
--- /dev/null
+++ b/CodingTracker.Tests/HelperTests/HelperTests.cs
@@ -0,0 +1,71 @@
+using CodingTracker.kilozdazolik;
+using FluentAssertions;
+using FluentAssertions.Extensions;
+
+namespace CodingTracker.Tests.Helpers;
+
+public class HelperTests
+{
+ private readonly Helper _helper;
+
+ public HelperTests()
+ {
+ _helper = new Helper();
+ }
+
+ [Fact]
+ public void ValidateDate_ValidFormat_ReturnsTrue()
+ {
+ //Arrange
+ string validDate = "08/12/2025 14:30:00";
+ DateTime parsedDate;
+
+ //Act
+ bool result = _helper.ValidateDate(validDate, out parsedDate);
+
+ //Assert
+ result.Should().BeTrue();
+ }
+
+ [Fact]
+ public void ValidateDate_InvalidFormat_ReturnsFalse()
+ {
+ //Arrange
+ string invalidDate = "2025.12.01";
+ DateTime parsedDate;
+
+ //Act
+ bool result = _helper.ValidateDate(invalidDate, out parsedDate);
+
+ //Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void IsSessionDatesValid_ValidFormat_ReturnsTrue()
+ {
+ //Arrange
+ var startDate = 1.March(2025).At(22, 15).AsLocal();
+ var endDate = 2.March(2025).At(22, 15).AsLocal();
+
+ //Act
+ bool result = _helper.IsSessionDatesValid(startDate,endDate);
+
+ //Assert
+ result.Should().BeTrue();
+ }
+
+ [Fact]
+ public void IsSessionDatesValid_InvalidFormat_ReturnsFalse()
+ {
+ //Arrange
+ var startDate = 20.March(2025).At(22, 15).AsLocal();
+ var endDate = 1.March(2025).At(22, 15).AsLocal();
+
+ //Act
+ bool result = _helper.IsSessionDatesValid(startDate,endDate);
+
+ //Assert
+ result.Should().BeFalse();
+ }
+}
diff --git a/CodingTracker.Tests/ServiceTests/TrackerServiceTests.cs b/CodingTracker.Tests/ServiceTests/TrackerServiceTests.cs
new file mode 100644
index 0000000..a3b516d
--- /dev/null
+++ b/CodingTracker.Tests/ServiceTests/TrackerServiceTests.cs
@@ -0,0 +1,51 @@
+using CodingTracker.kilozdazolik.Models;
+using CodingTracker.kilozdazolik.Services;
+using Dapper;
+using FluentAssertions;
+using FluentAssertions.Extensions;
+using Microsoft.Data.Sqlite;
+
+namespace CodingTracker.Tests.Services;
+
+public class TrackerServiceTests : IDisposable
+{
+ private const string TestConnectionString = "Data Source=CodingTrackerTest;Mode=Memory;Cache=Shared";
+ private readonly SqliteConnection _keepAliveConnection;
+
+ public TrackerServiceTests()
+ {
+ _keepAliveConnection = new SqliteConnection(TestConnectionString);
+ _keepAliveConnection.Open();
+ _keepAliveConnection.Execute(@"
+ CREATE TABLE IF NOT EXISTS CodingTracker (
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ StartTime TEXT,
+ EndTime TEXT,
+ Duration TEXT
+ )");
+ }
+
+ [Fact]
+ public void InsertSession_StoresData_InMemory()
+ {
+ //Arrange
+ var service = new TrackerService(TestConnectionString);
+
+ var startTime = DateTime.Now;
+ var endTime = startTime.AddHours(5);
+
+ //Act
+ service.InsertSession(startTime, endTime);
+
+ //Assert
+ var result = _keepAliveConnection.QuerySingle("SELECT * FROM CodingTracker");
+
+ result.Should().NotBeNull();
+ result.Duration.Should().NotBe(200.Milliseconds());
+ }
+
+ public void Dispose()
+ {
+ _keepAliveConnection.Close();
+ }
+}
\ No newline at end of file
diff --git a/CodingTracker.Tests/obj/Debug/net10.0/CodingTracker.Tests.GlobalUsings.g.cs b/CodingTracker.Tests/obj/Debug/net10.0/CodingTracker.Tests.GlobalUsings.g.cs
new file mode 100644
index 0000000..fe43752
--- /dev/null
+++ b/CodingTracker.Tests/obj/Debug/net10.0/CodingTracker.Tests.GlobalUsings.g.cs
@@ -0,0 +1,9 @@
+//
+global using System;
+global using System.Collections.Generic;
+global using System.IO;
+global using System.Linq;
+global using System.Net.Http;
+global using System.Threading;
+global using System.Threading.Tasks;
+global using Xunit;
diff --git a/CodingTracker.kilozdazolik/CodingTracker.kilozdazolik.csproj b/CodingTracker.kilozdazolik/CodingTracker.kilozdazolik.csproj
new file mode 100644
index 0000000..737c485
--- /dev/null
+++ b/CodingTracker.kilozdazolik/CodingTracker.kilozdazolik.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/CodingTracker.kilozdazolik/Controllers/TrackerController.cs b/CodingTracker.kilozdazolik/Controllers/TrackerController.cs
new file mode 100644
index 0000000..efdb733
--- /dev/null
+++ b/CodingTracker.kilozdazolik/Controllers/TrackerController.cs
@@ -0,0 +1,173 @@
+using System.Diagnostics;
+using CodingTracker.kilozdazolik.Services;
+using Spectre.Console;
+using CodingTracker.kilozdazolik.Models;
+namespace CodingTracker.kilozdazolik.Controllers;
+
+public class TrackerController
+{
+ private readonly ITrackerService _trackerService;
+ private readonly Helper _helper = new();
+
+ public TrackerController(ITrackerService trackerService)
+ {
+ _trackerService = trackerService;
+ }
+
+ public void StartSession()
+ {
+ AnsiConsole.MarkupLine("Press the Enter key to begin/stop:");
+ Console.ReadLine();
+ DateTime startTime = DateTime.Now;
+ Stopwatch sw = Stopwatch.StartNew();
+
+ while (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.Enter)
+ {
+ Console.Clear();
+ TimeSpan ts = sw.Elapsed;
+ AnsiConsole.Markup($"Elapsed time: {ts.Hours:D2}:{ts.Minutes:D2}:{ts.Seconds:D2}");
+ Thread.Sleep(1000);
+ }
+
+ sw.Stop();
+ DateTime endTime = DateTime.Now;
+
+ Console.Clear();
+ AnsiConsole.MarkupLine($"Final elapsed time: {sw.Elapsed.Hours:D2}:{sw.Elapsed.Minutes:D2}:{sw.Elapsed.Seconds:D2}");
+
+ _trackerService.InsertSession(startTime, endTime);
+
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+ }
+ public void AddSession()
+ {
+ bool confirm = false;
+ while (!confirm)
+ {
+ AnsiConsole.MarkupLine("[yellow]Add a new coding session[/]");
+
+ var inputStartDate = AnsiConsole.Prompt(
+ new TextPrompt("Please write the starting date in this format: (dd/MM/yyyy HH:mm:ss)"));
+ if (!_helper.ValidateDate(inputStartDate, out DateTime startDate))
+ {
+ AnsiConsole.MarkupLine("[red]Invalid start date format![/]");
+ continue;
+ }
+
+ var inputEndDate = AnsiConsole.Prompt(
+ new TextPrompt("Please write the ending date in this format: (dd/MM/yyyy HH:mm:ss)"));
+ if (!_helper.ValidateDate(inputEndDate, out DateTime endDate))
+ {
+ AnsiConsole.MarkupLine("[red]Invalid start date format![/]");
+ continue;
+ }
+
+ if (_helper.IsSessionDatesValid(startDate, endDate))
+ {
+ _trackerService.InsertSession(startDate, endDate);
+ confirm = true;
+ AnsiConsole.MarkupLine("[green]Session successfully added![/]");
+ }
+ }
+ }
+
+ public void ViewAllSessions()
+ {
+ List allSessions = _trackerService.GetAllSession();
+
+ if (allSessions.Any())
+ {
+ _helper.CreateTable(allSessions);
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("I could not find any session.");
+ }
+
+ }
+ public void DeleteSession()
+ {
+ List allSession = _trackerService.GetAllSession();
+
+ if (allSession.Count == 0)
+ {
+ AnsiConsole.MarkupLine("[red]No session is available to delete.[/]");
+ Console.ReadKey();
+ }
+
+ var sessionToDelete = AnsiConsole.Prompt(new SelectionPrompt()
+ .Title("Select a [red]session[/] to delete:").UseConverter(s => $"Start: {s.StartTime} | End: {s.EndTime} - {s.Duration} elapsed.").AddChoices(allSession));
+
+ if (_helper.ConfirmMessage("Delete", sessionToDelete.StartTime.ToString()))
+ {
+ _trackerService.DeleteSession(sessionToDelete);
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("Deletion Canceled.");
+ }
+
+ AnsiConsole.MarkupLine("[green]Press Any Key to Continue.[/]");
+ Console.ReadKey();
+ }
+ public void EditSession()
+ {
+ List allSession = _trackerService.GetAllSession();
+
+ if (allSession.Count == 0)
+ {
+ AnsiConsole.MarkupLine("[red]No session is available to delete.[/]");
+ Console.ReadKey();
+ }
+
+ var sessionToEdit = AnsiConsole.Prompt(new SelectionPrompt()
+ .Title("Select a [cyan]session[/] to edit:").UseConverter(s => $"Start: {s.StartTime} | End: {s.EndTime} - {s.Duration} elapsed.").AddChoices(allSession));
+
+ Console.Clear();
+
+ bool confirm = false;
+ while (!confirm)
+ {
+ var inputStartDate = AnsiConsole.Prompt(
+ new TextPrompt($"Enter the [green]new start time[/] (dd/MM/yyyy HH:mm:ss)\n[grey](default: {sessionToEdit.StartTime:dd/MM/yyyy HH:mm:ss})[/]"));
+ if (!_helper.ValidateDate(inputStartDate, out DateTime newStartingTime))
+ {
+ AnsiConsole.MarkupLine("[red]Invalid start date format![/]");
+ continue;
+ }
+
+ var inputEndDate = AnsiConsole.Prompt(
+ new TextPrompt($"Enter the [green]new end time[/] (dd/MM/yyyy HH:mm:ss)\n[grey](default: {sessionToEdit.EndTime:dd/MM/yyyy HH:mm:ss})[/]"));
+ if (!_helper.ValidateDate(inputEndDate, out DateTime newEndingTime))
+ {
+ AnsiConsole.MarkupLine("[red]Invalid end date format![/]");
+ continue;
+ }
+
+ if (_helper.IsSessionDatesValid(newStartingTime, newEndingTime))
+ {
+ sessionToEdit.StartTime = newStartingTime;
+ sessionToEdit.EndTime = newEndingTime;
+ confirm = true;
+ }
+ }
+
+ _trackerService.UpdateSession(sessionToEdit);
+ }
+
+ public void ViewSessionsByDate(bool ascending)
+ {
+ List allSessions = _trackerService.GetSessionsOrderByDate(ascending);
+
+ if (allSessions.Any())
+ {
+ _helper.CreateTable(allSessions);
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("I could not find any session.");
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/CodingTracker.kilozdazolik/Data/Database.cs b/CodingTracker.kilozdazolik/Data/Database.cs
new file mode 100644
index 0000000..dbe6200
--- /dev/null
+++ b/CodingTracker.kilozdazolik/Data/Database.cs
@@ -0,0 +1,82 @@
+using System.Data;
+using Dapper;
+using Microsoft.Data.Sqlite;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.Json;
+
+namespace CodingTracker.kilozdazolik.Data;
+
+internal static class Database
+{
+ private static readonly IConfiguration _config;
+
+ static Database()
+ {
+ _config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json").Build();
+ }
+
+ public static string ConnectionString(string name)
+ {
+ return _config.GetConnectionString(name);
+ }
+
+ public static void CreateDatabase()
+ {
+ using (IDbConnection db = new SqliteConnection(ConnectionString("DefaultConnection")))
+ {
+ string createTable = @"
+ CREATE TABLE IF NOT EXISTS CodingTracker(
+ ID INTEGER PRIMARY KEY,
+ StartTime TEXT NOT NULL,
+ EndTime TEXT NOT NULL,
+ Duration TEXT
+ )";
+
+ db.Execute(createTable);
+ }
+ }
+
+ public static void InsertDummyData()
+{
+ using (IDbConnection db = new SqliteConnection(ConnectionString("DefaultConnection")))
+ {
+ int existingCount = db.QuerySingle("SELECT COUNT(*) FROM CodingTracker");
+
+ if (existingCount > 0)
+ {
+ return;
+ }
+
+ var random = new Random();
+ var insertSql = @"
+ INSERT INTO CodingTracker (StartTime, EndTime, Duration)
+ VALUES (@StartTime, @EndTime, @Duration)";
+
+ var dummyData = new List