Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions ActionExecuter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Coding.Tracker.Commands;
using Spectre.Console;

namespace Coding.Tracker
{

public static class ActionExecuter
{
private readonly static string startSession = "Start new session";
private readonly static string addSession = "Add new session";
private readonly static string viewSessions = "View sessions";
private readonly static string updateSession = "Update session";
private readonly static string deleteSession = "Delete session";
private readonly static string exit = "Exit";

private readonly static Dictionary<string, UserAction> dictionary = new()
{
{ startSession, UserAction.StartSession },
{ addSession, UserAction.CreateSession },
{ viewSessions, UserAction.ReadSessions },
{ updateSession, UserAction.UpdateSession },
{ deleteSession, UserAction.DeleteSession },
{ exit, UserAction.Exit },
};

public static UserAction GetUserAction()
{
var choice = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("What [blue]action[/] would you like?")
.AddChoices(
startSession,
addSession,
viewSessions,
updateSession,
deleteSession,
exit)
);
return dictionary[choice];
}

public static void Execute(UserAction action, CodeSessionService service)
{
Command command = default;
if (action == UserAction.CreateSession)
command = new CreateSessionCommand(service);

else if (action == UserAction.StartSession)
command = new StartSessionCommand(service);

else if (action == UserAction.ReadSessions)
command = new ViewDataCommand(service);

else if (action == UserAction.UpdateSession)
command = new UpdateSessionCommand(service);

else if (action == UserAction.DeleteSession)
command = new DeleteSessionCommand(service);
else if (action == UserAction.Exit)
return;
else throw new ArgumentException("Invalid command");
command.Execute();
}
}
}
15 changes: 15 additions & 0 deletions CodeSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Coding.Tracker
{
public class CodeSession
{
public int Id { get; set; }
public string StartTime { get; set; }
public string EndTime { get; set; }
public string Duration { get; set; }

public override string ToString()
{
return Id.ToString()+ ":" + StartTime + " " + EndTime + " " + Duration;
}
}
}
71 changes: 71 additions & 0 deletions CodeSessionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Dapper;
using Microsoft.Data.Sqlite;
using System.Data;

namespace Coding.Tracker
{
public class CodeSessionService : ICodeSessionService
{
private readonly string table = "sessions";
private readonly string connectionString;
public CodeSessionService(string connectionString)
{
this.connectionString = connectionString;
}

public bool CreateDatabase()
{
return Execute(db =>
{
var query = $@"CREATE TABLE IF NOT EXISTS {table}
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
StartTime TEXT,
EndTime TEXT,
Duration TEXT
)";
return db.Execute(query) > 0;
});
}

public bool Create(CodeSession session)
{
return Execute(db =>
{
var query = $"INSERT INTO {table}(StartTime, EndTime, Duration) VALUES(@StartTime, @EndTime, @Duration)";
return db.Execute(query, session) > 0;
});
}

public List<CodeSession> ReadAllData()
{
using IDbConnection db = new SqliteConnection(connectionString);
var query = $"SELECT * FROM {table}";
return db.Query<CodeSession>(query).ToList();
}

public bool Update(CodeSession session)
{
return Execute(db =>
{
var query = $"UPDATE {table} SET StartTime=@StartTime, EndTime=@EndTime, Duration=@Duration WHERE Id=@Id";
return db.Execute(query, session) > 0;
});
}

public bool Delete(int id)
{
return Execute(db =>
{
var query = $"DELETE FROM {table} WHERE Id=@id";
return db.Execute(query, new { id }) > 0;
});
}

private bool Execute(Func<IDbConnection, bool> action)
{
using IDbConnection db = new SqliteConnection(connectionString);
return action(db);
}
}
}
152 changes: 152 additions & 0 deletions CodeTracker_should.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using Microsoft.Data.Sqlite;
using NUnit.Framework;

namespace Coding.Tracker
{
[TestFixture]
public class CodeTracker_should
{
private const string DatabaseFile = "coding_tracker_tests.db";
private CodeSessionService database = null!;

[Test]
public void CreateDatabaseTest()
{
var session = new CodeSession
{
StartTime = "2026-09-21 10:00",
EndTime = "2026-09-21 11:00",
Duration = "01:00:00"
};

database.Create(session);
database.CreateDatabase();

Assert.That(database.ReadAllData().Count, Is.EqualTo(1));
}

[TestCase("2026-09-21 10:00", "2026-09-21 11:00", "01:00:00")]
public void CreateRecordTest(string startTime, string endTime, string duration)
{
var session = new CodeSession
{
StartTime = startTime,
EndTime = endTime,
Duration = duration
};

var result = database.Create(session);
var added = database.ReadAllData().Single();

Assert.That(result, Is.True);
Assert.That(added.Id, Is.GreaterThan(0));
Assert.That(added.StartTime, Is.EqualTo(session.StartTime));
Assert.That(added.EndTime, Is.EqualTo(session.EndTime));
Assert.That(added.Duration, Is.EqualTo(session.Duration));
}

[Test]
public void ReadRecordsTest()
{
database.Create(new CodeSession
{
StartTime = "2026-09-21 10:00",
EndTime = "2026-09-21 11:00",
Duration = "01:00:00"
});

database.Create(new CodeSession
{
StartTime = "2026-09-22 10:00",
EndTime = "2026-09-22 12:00",
Duration = "02:00:00"
});

var records = database.ReadAllData();

Assert.That(records.Count, Is.EqualTo(2));
Assert.That(
records.Select(r => r.Duration),
Is.EquivalentTo(new[] { "01:00:00", "02:00:00" }));
}

[Test]
public void DeleteRecordTest()
{
database.Create(new CodeSession
{
StartTime = "2026-09-21 10:00",
EndTime = "2026-09-21 11:00",
Duration = "01:00:00"
});

database.Create(new CodeSession
{
StartTime = "2026-09-22 10:00",
EndTime = "2026-09-22 12:00",
Duration = "02:00:00"
});

var records = database.ReadAllData();
var toDelete = records.Single(r => r.Duration == "01:00:00");
var toKeep = records.Single(r => r.Duration == "02:00:00");

var result = database.Delete(toDelete.Id);
var remaining = database.ReadAllData().Single();

Assert.That(result, Is.True);
Assert.That(remaining.Id, Is.EqualTo(toKeep.Id));
Assert.That(remaining.Duration, Is.EqualTo(toKeep.Duration));
}

[Test]
public void UpdateRecordTest()
{
database.Create(new CodeSession
{
StartTime = "2026-09-21 10:00",
EndTime = "2026-09-21 11:00",
Duration = "01:00:00"
});

var original = database.ReadAllData().Single();

var updatedSession = new CodeSession
{
Id = original.Id,
StartTime = "2026-09-22 12:00",
EndTime = "2026-09-22 14:30",
Duration = "02:30:00"
};

var result = database.Update(updatedSession);
var updated = database.ReadAllData().Single();

Assert.That(result, Is.True);
Assert.That(updated.Id, Is.EqualTo(original.Id));
Assert.That(updated.StartTime, Is.EqualTo(updatedSession.StartTime));
Assert.That(updated.EndTime, Is.EqualTo(updatedSession.EndTime));
Assert.That(updated.Duration, Is.EqualTo(updatedSession.Duration));
}

[SetUp]
public void Setup()
{
DeleteDatabaseFile();
database = new CodeSessionService($"Data Source={DatabaseFile}");
database.CreateDatabase();
}

[TearDown]
public void TearDown()
{
DeleteDatabaseFile();
}

private static void DeleteDatabaseFile()
{
SqliteConnection.ClearAllPools();
File.Delete(DatabaseFile);
}
}
}
26 changes: 26 additions & 0 deletions Coding.Tracker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<StartupObject>Coding.Tracker.Program</StartupObject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.89" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="11.0.0-rc.1.26425.128" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.12" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageReference Include="NUnit" Version="4.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="6.3.0" />
<PackageReference Include="Spectre.Console" Version="0.57.3-alpha.0.19" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
39 changes: 39 additions & 0 deletions Commands/Command.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Globalization;

namespace Coding.Tracker.Commands
{
public abstract class Command
{
protected CodeSessionService service;
protected Command(CodeSessionService service)
{
this.service = service;
}
public abstract void Execute();

protected static DateTime ValidateDateTime(string input)
{
var valid = DateTime.TryParseExact(
input,
"dd-MM-yy HH:mm:ss",
new CultureInfo("en-US"),
DateTimeStyles.None, out DateTime result);

if (valid)
{
return result;
}
else throw new FormatException($"Wrong format of value: {input}! Should be dd-MM-yy HH:mm:ss");
}

protected static int ValidateNumeric(string input)
{
var valid = int.TryParse(input, out int result);
if (valid)
{
return result;
}
else throw new FormatException($"Wrong format of value: {input}! Should be numeric");
}
}
}
Loading