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
63 changes: 63 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
46 changes: 46 additions & 0 deletions CodingTracker.UnitTests/CodingSessionRepositoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Extensions.Configuration;
using System.Globalization;

namespace CodingTracker.UnitTests
{
public class CodingSessionRepositoryTests
{
[SetUp]
public void Setup()
{
var config = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.Build();

DatabaseManager.SetConfiguration(config);
DatabaseManager.Start();
DatabaseManager.EmptyDb();
}

[TestCase("Coding Tracker", "C#", 2026, 9, 21, 18, 30, 0, 2026, 9, 21, 20, 15, 0)]
[TestCase("Videogame", "C++", 2026, 9, 22, 10, 0, 0, 2026, 9, 22, 12, 45, 0)]
public void CRUD_CodingSessions(string project, string language, int year1, int month1, int day1, int hours1, int minutes1, int seconds1, int year2, int month2, int day2, int hours2, int minutes2, int seconds2)
{
List<CodingSession> newSessions = new()
{
new() { Project = project, Language = language,
StartTime = new DateTime(year1, month1, day1, hours1, minutes1, seconds1),
EndTime = new DateTime(year2, month2, day2, hours2, minutes2, seconds2) },
};

CodingSessionRepository.InsertSessions(newSessions);
var sessions = CodingSessionRepository.GetSessions();
Assert.That(sessions, Is.Not.Empty);

foreach(var session in sessions)
{
int rowsDeleted = CodingSessionRepository.DeleteSession(session.Id);
Assert.That(rowsDeleted, Is.EqualTo(1));
}

var empty = CodingSessionRepository.GetSessions();
Assert.That(empty, Is.Empty);
}
}
}
27 changes: 27 additions & 0 deletions CodingTracker.UnitTests/CodingTracker.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CodingTracker\CodingTracker.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions CodingTracker.UnitTests/InputValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Globalization;

namespace CodingTracker.UnitTests
{
public class InputValidationTests
{
private static readonly object[] ValidDateTimeCases =
{
new TestCaseData("2026-12-12 10:05:13", true),
new TestCaseData("2025-13-12 20:12:26", false),
new TestCaseData("2021-10-44 01:02:03", false),
new TestCaseData("2021-10-12 60:70:80", false),

new TestCaseData("2021-10-12", false),
new TestCaseData("", false),
new TestCaseData("asdf", false),
new TestCaseData(null, false)
};

[TestCaseSource(nameof(ValidDateTimeCases))]
public void ValidDateTime_ReturnsTrue(string inputDate, bool expectedResult)
{
bool isValidDate = InputValidation.ValidateDateTime(inputDate, out DateTime dateValidated);
Assert.That(isValidDate, Is.EqualTo(expectedResult));
}

[TestCase("2026-12-12 10:05:13", "2026-12-12 10:05:14", true)]
[TestCase("2026-12-12 10:05:13", "2026-12-12 10:05:13", true)]
[TestCase("2026-12-12 10:05:13", "2026-12-12 10:05:12", false)]
public void ValidEndTime_ReturnsTrue(string startDate, string endDate, bool expectedResult)
{
DateTime start = DateTime.ParseExact(startDate, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None);
DateTime end = DateTime.ParseExact(endDate, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None);
bool isValidDate = InputValidation.ValidateEndTime(start, end);
Assert.That(isValidDate, Is.EqualTo(expectedResult));
}
}
}
8 changes: 8 additions & 0 deletions CodingTracker.UnitTests/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"CodingTracker.UnitTests": {
"commandName": "Project",
"workingDirectory": "$(ProjectDir)"
}
}
}
4 changes: 4 additions & 0 deletions CodingTracker.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<Solution>
<Project Path="CodingTracker.UnitTests/CodingTracker.UnitTests.csproj" Id="b037cc30-31d2-4524-85d4-29a4388f92f8" />
<Project Path="CodingTracker/CodingTracker.csproj" />
</Solution>
25 changes: 25 additions & 0 deletions CodingTracker/CodingSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace CodingTracker
{
public class CodingSession
{
public int Id { get; set; }
public string Project { get; set; } = "";
public string Language { get; set; } = "";
public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; }
public TimeSpan Duration => EndTime == null ? DateTime.Now - StartTime : EndTime.Value - StartTime;

public CodingSession()
{

}

public CodingSession(string project, string language, DateTime startTime, DateTime? endTime)
{
Project = project;
Language = language;
StartTime = startTime;
EndTime = endTime;
}
}
}
35 changes: 35 additions & 0 deletions CodingTracker/CodingSessionRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Dapper;
using Microsoft.Data.Sqlite;

namespace CodingTracker
{
public static class CodingSessionRepository
{
public static List<CodingSession> GetSessions()
{
using SqliteConnection connection = DatabaseManager.CreateConnection();
var sessions = connection.Query<CodingSession>("SELECT * FROM CodingSessions");
return sessions.ToList();
}

public static int InsertSessions(List<CodingSession> sessions)
{
using SqliteConnection connection = DatabaseManager.CreateConnection();
connection.Open();
using SqliteTransaction transaction = connection.BeginTransaction();

string query = "INSERT INTO CodingSessions (Project, Language, StartTime, EndTime) VALUES (@Project, @Language, @StartTime, @EndTime)";
int rowsInserted = connection.Execute(query, sessions, transaction);

transaction.Commit();
return rowsInserted;
}

public static int DeleteSession(int id)
{
using SqliteConnection connection = DatabaseManager.CreateConnection();
string query = "DELETE FROM CodingSessions WHERE Id = @Id";
return connection.Execute(query, new { Id = id });
}
}
}
25 changes: 25 additions & 0 deletions CodingTracker/CodingTracker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.89" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.12" />
<PackageReference Include="Spectre.Console" Version="0.57.2" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>

</Project>
74 changes: 74 additions & 0 deletions CodingTracker/DatabaseManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Dapper;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;

namespace CodingTracker
{
public static class DatabaseManager
{
private static IConfiguration _config = null!;
private static string _connectionString = "";

public static void SetConfiguration(IConfiguration config)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}

internal static SqliteConnection CreateConnection()
{
return new(_connectionString);
}

public static void Start()
{
using SqliteConnection connection = CreateConnection();
string query =
"""
CREATE TABLE IF NOT EXISTS CodingSessions (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Project TEXT,
Language TEXT,
StartTime TEXT,
EndTime TEXT
)
""";
connection.Execute(query);
}

/// <summary>
/// Inserts some default values for the user to view
/// </summary>
internal static void Initialize()
{
if (IsEmpty())
{
List<CodingSession> sessions = new()
{
new(".NET Course", "C#", new DateTime(2026, 9, 22, 18, 30, 0), null),
new() { Project = "Coding Tracker", Language = "C#",
StartTime = new DateTime(2026, 9, 21, 18, 30, 0),
EndTime = new DateTime(2026, 9, 21, 20, 15, 0) },
new() { Project = "Platformer", Language = "C++",
StartTime = new DateTime(2026, 9, 22, 10, 0, 0),
EndTime = new DateTime(2026, 9, 22, 12, 45, 0) }
};
CodingSessionRepository.InsertSessions(sessions);
}
}

private static bool IsEmpty()
{
using SqliteConnection connection = CreateConnection();
int count = connection.ExecuteScalar<int>("SELECT EXISTS (SELECT 1 FROM CodingSessions)");
return count == 0;
}

public static void EmptyDb()
{
using SqliteConnection connection = CreateConnection();
string query = "DELETE FROM CodingSessions";
connection.Execute(query);
}
}
}
32 changes: 32 additions & 0 deletions CodingTracker/InputValidation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Globalization;

namespace CodingTracker
{
public static class InputValidation
{
/// <summary>
/// Checks if date is valid
/// </summary>
/// <param name="dateInput"></param>
/// <returns></returns>
public static bool ValidateDateTime(string dateInput, out DateTime dateValidated)
{
if (dateInput == null)
{
dateValidated = default;
return false;
}
bool validDate = false;
if (DateTime.TryParseExact(dateInput, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateValidated))
{
validDate = true;
}
return validDate;
}

public static bool ValidateEndTime(DateTime startTime, DateTime endTime)
{
return (endTime - startTime).TotalSeconds >= 0;
}
}
}
Loading
Loading