diff --git a/CodingTracker.urasylmaz1/CodingTracker.urasylmaz1.csproj b/CodingTracker.urasylmaz1/CodingTracker.urasylmaz1.csproj new file mode 100644 index 000000000..8c9271666 --- /dev/null +++ b/CodingTracker.urasylmaz1/CodingTracker.urasylmaz1.csproj @@ -0,0 +1,28 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + PreserveNewest + + + + + + + + + + + + diff --git a/CodingTracker.urasylmaz1/CodingTracker.urasylmaz1.slnx b/CodingTracker.urasylmaz1/CodingTracker.urasylmaz1.slnx new file mode 100644 index 000000000..5ff8e6c4a --- /dev/null +++ b/CodingTracker.urasylmaz1/CodingTracker.urasylmaz1.slnx @@ -0,0 +1,3 @@ + + + diff --git a/CodingTracker.urasylmaz1/Controllers/CodingController.cs b/CodingTracker.urasylmaz1/Controllers/CodingController.cs new file mode 100644 index 000000000..086a2512e --- /dev/null +++ b/CodingTracker.urasylmaz1/Controllers/CodingController.cs @@ -0,0 +1,150 @@ +using CodingTracker.urasylmaz1.Helpers; +using CodingTracker.urasylmaz1.Models; +using Dapper; +using Microsoft.Data.Sqlite; +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.urasylmaz1.Controllers +{ + public class CodingController + { + private readonly string _connectionString; + public CodingController(string connectionString) + { + _connectionString = connectionString; + } + + public void Run() + { + bool running = true; + + while (running) + { + Console.Clear(); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[green]Coding Tracker[/]") + .AddChoices( + "Add Session", + "View Sessions", + "Delete Session", + "Exit" + ) + ); + + switch (choice) + { + case "Add Session": + AddSession(); + break; + + case "View Sessions": + ReadSession(); + break; + + case "Delete Session": + RemoveSession(); + break; + + case "Exit": + running = false; + break; + } + + if (running) + { + AnsiConsole.MarkupLine( + "\n[yellow]Press any key to continue...[/]" + ); + + Console.ReadKey(); + } + } + } + + public void AddSession() + { + DateTime start = + UserInput.GetDateTime( + "Enter start time (yyyy-MM-dd HH:mm):" + ); + + DateTime end = + UserInput.GetDateTime( + "Enter end time (yyyy-MM-dd HH:mm):" + ); + + string duration = (end - start).ToString(); + + CodingSession session = new() + { + StartTime = start, + EndTime = end, + Duration = duration + }; + + using var connection = + new SqliteConnection(_connectionString); + + string sql = @" + INSERT INTO CodingSessions + (StartTime, EndTime, Duration) + VALUES + (@StartTime, @EndTime, @Duration) + "; + + connection.Execute(sql, session); + } + + public void ReadSession() + { + using var connection = new SqliteConnection(_connectionString); + List sessions = connection.Query("SELECT * FROM CodingSessions").AsList(); + var table = new Table(); + + table.AddColumn("Id"); + table.AddColumn("Start Time"); + table.AddColumn("End Time"); + table.AddColumn("Duration"); + + foreach (var session in sessions) + { + table.AddRow( + session.Id.ToString(), + session.StartTime.ToString(), + session.EndTime.ToString(), + session.Duration.ToString() + ); + } + + AnsiConsole.Write(table); + } + + public void RemoveSession() + { + ReadSession(); + + int id = AnsiConsole.Ask("Enter session Id to delete:"); + + bool exists = Validation.SessionExists(_connectionString, id ); + + if (!exists) + { + AnsiConsole.MarkupLine("[red]Session not found.[/]" ); + return; + } + + using var connection = new SqliteConnection(_connectionString); + + string sql ="DELETE FROM CodingSessions WHERE Id = @Id"; + + connection.Execute(sql, new { Id = id }); + + AnsiConsole.MarkupLine("[green]Session deleted.[/]"); + } + } +} diff --git a/CodingTracker.urasylmaz1/Data/Database.cs b/CodingTracker.urasylmaz1/Data/Database.cs new file mode 100644 index 000000000..233a5c6a3 --- /dev/null +++ b/CodingTracker.urasylmaz1/Data/Database.cs @@ -0,0 +1,38 @@ +using Dapper; +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.urasylmaz1.Data +{ + public class Database + { + private readonly string _connectionString; + public Database(string connectionString) + { + _connectionString = connectionString; + } + + public void Initialize() + { + // using Dapper instead of raw ADO.NET for simplicity + + using var connection = + new SqliteConnection(_connectionString); + + string sql = @" + CREATE TABLE IF NOT EXISTS CodingSessions + ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + StartTime TEXT, + EndTime TEXT, + Duration TEXT + ); + "; + + connection.Execute(sql); + } + + } +} diff --git a/CodingTracker.urasylmaz1/Helpers/UserInput.cs b/CodingTracker.urasylmaz1/Helpers/UserInput.cs new file mode 100644 index 000000000..3e2e58300 --- /dev/null +++ b/CodingTracker.urasylmaz1/Helpers/UserInput.cs @@ -0,0 +1,36 @@ +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace CodingTracker.urasylmaz1.Helpers +{ + public class UserInput + { + public static DateTime GetDateTime(string message) + { + while (true) + { + string input = AnsiConsole.Ask(message); + + if (Validation.IsValidDateTime(input)) + { + return DateTime.ParseExact( + input, + "yyyy-MM-dd HH:mm", + null + ); + } + + AnsiConsole.MarkupLine( + "[red]Invalid format![/]" + ); + + AnsiConsole.MarkupLine( + "[yellow]Use format: yyyy-MM-dd HH:mm[/]" + ); + } + } + } +} diff --git a/CodingTracker.urasylmaz1/Helpers/Validation.cs b/CodingTracker.urasylmaz1/Helpers/Validation.cs new file mode 100644 index 000000000..22358662e --- /dev/null +++ b/CodingTracker.urasylmaz1/Helpers/Validation.cs @@ -0,0 +1,27 @@ +using CodingTracker.urasylmaz1.Models; +using Dapper; +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.urasylmaz1.Helpers +{ + public class Validation + { + public static bool IsValidDateTime(string input) + { + return DateTime.TryParseExact(input, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out _); // TryParseExact returns true if parsing is successful, false otherwise + } + public static bool SessionExists(string connectionString, int id) + { + using var connection = new SqliteConnection(connectionString); + + string sql ="SELECT * FROM CodingSessions WHERE Id = @Id"; + + var session = connection.QuerySingleOrDefault(sql,new { Id = id } ); + + return session != null; + } + } +} diff --git a/CodingTracker.urasylmaz1/Models/CodingSession.cs b/CodingTracker.urasylmaz1/Models/CodingSession.cs new file mode 100644 index 000000000..ec844377c --- /dev/null +++ b/CodingTracker.urasylmaz1/Models/CodingSession.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.urasylmaz1.Models +{ + public class CodingSession + { + public int Id { get; set; } + + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + + public string Duration { get; set; } = string.Empty; + } +} diff --git a/CodingTracker.urasylmaz1/Program.cs b/CodingTracker.urasylmaz1/Program.cs new file mode 100644 index 000000000..888cd05c9 --- /dev/null +++ b/CodingTracker.urasylmaz1/Program.cs @@ -0,0 +1,33 @@ +using CodingTracker.urasylmaz1.Controllers; +using CodingTracker.urasylmaz1.Data; +using Microsoft.Extensions.Configuration; + +namespace CodingTracker.urasylmaz1 +{ + class Program + { + + static void Main(string[] args) + { + + var config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + string? connectionString = config.GetConnectionString("DefaultConnection"); + + if (string.IsNullOrWhiteSpace(connectionString)) + { + Console.WriteLine("Connection string missing."); + return; + } + + Database db = new(connectionString); + + db.Initialize(); + + CodingController controller = new(connectionString); + + controller.Run(); + } + } +} diff --git a/CodingTracker.urasylmaz1/appsettings.json b/CodingTracker.urasylmaz1/appsettings.json new file mode 100644 index 000000000..bc8e9743d --- /dev/null +++ b/CodingTracker.urasylmaz1/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=codingtracker.db" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..91d2f91a0 --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ +# CodingTracker + +A console-based application for tracking and managing coding sessions with persistent storage capabilities. + +## Overview + +CodingTracker is a .NET 10.0 console application designed to help developers monitor their coding activity by recording session start times, end times, and calculating session durations. The application provides a user-friendly menu interface for managing coding session data. + +## Design Thought Process + +### 1. **Architecture Pattern: Layered Architecture** + +The project follows a clean, layered architecture approach with separation of concerns: + +``` +Program.cs (Entry Point) + ↓ +CodingController (Presentation Layer) + ↓ +Database (Data Access Layer) + ↓ +Models (Domain Layer) +``` + +**Rationale:** +- **Separation of Concerns**: Each layer has a single responsibility +- **Maintainability**: Changes in one layer don't cascade through the entire application +- **Testability**: Each component can be tested independently +- **Scalability**: New features can be added without restructuring existing code + +### 2. **Database Strategy: SQLite** + +The application uses SQLite combined with Dapper for data persistence. + +**Why SQLite?** +- Lightweight and file-based—no external database server needed +- Perfect for console applications and small-to-medium projects +- Easy deployment and distribution +- Excellent for development and prototyping + +### 3. **Configuration Management** + +The application uses `appsettings.json` with Microsoft.Extensions.Configuration. + +**Benefits:** +- Externalized configuration separate from code +- Environment-specific settings can be easily swapped +- Connection strings are not hardcoded +- Follows .NET best practices and conventions + +### 4. **User Interface: Spectre.Console** + +Rich console formatting is implemented using Spectre.Console. + +**Advantages:** +- Enhanced user experience with colors, prompts, and structured menus +- Professional-looking console output +- Cross-platform compatibility +- Improved readability and user engagement + +### 5. **Data Model: CodingSession** + +The `CodingSession` class represents the core domain entity. + +**Properties:** +- `Id`: Unique identifier for each session +- `StartTime`: When the coding session began +- `EndTime`: When the coding session ended +- `Duration`: Calculated session length (formatted string) + +**Design Decision:** +- Simple, focused model with only essential properties +- Duration stored as string for display purposes +- Follows POCO (Plain Old CLR Object) pattern + +### 6. **Helper Utilities** + +Separate utility classes handle cross-cutting concerns: + +- **Validation.cs**: Input validation and business logic constraints +- **UserInput.cs**: User input collection and parsing + +**Rationale:** +- Keeps controller logic clean and focused +- Reusable validation logic across the application +- Single responsibility principle + +### 7. **Initialization Strategy** + +Database initialization happens during startup (`db.Initialize()`). + +**Purpose:** +- Creates tables if they don't exist +- Ensures data consistency +- Establishes the database schema + +## Project Structure + +``` +CodingTracker.urasylmaz1/ +├── Program.cs # Application entry point +├── appsettings.json # Configuration file +├── CodingTracker.urasylmaz1.csproj # Project file +├── Models/ +│ └── CodingSession.cs # Domain entity +├── Controllers/ +│ └── CodingController.cs # Application logic & UI +├── Data/ +│ └── Database.cs # Data access layer +└── Helpers/ + ├── Validation.cs # Input validation + └── UserInput.cs # User input handling +``` + +## Technology Stack + +| Technology | Purpose | Version | +|-----------|---------|---------| +| .NET | Runtime Framework | 10.0 | +| Dapper | ORM & Data Access | 2.1.72 | +| SQLite | Database | Latest | +| Spectre.Console | Rich Console UI | 0.55.2 | +| Microsoft.Extensions.Configuration | Configuration Management | 10.0.7 | + +## Key Design Principles Applied + +### 1. **SOLID Principles** + +- **Single Responsibility**: Each class has one reason to change +- **Open/Closed**: Open for extension, closed for modification +- **Dependency Inversion**: Depends on abstractions where possible + +### 2. **DRY (Don't Repeat Yourself)** + +- Common functionality extracted into helper classes +- Validation logic centralized +- Connection string management unified + +### 3. **KISS (Keep It Simple, Stupid)** + +- Straightforward class structures without unnecessary complexity +- Clear naming conventions +- Minimal external dependencies + +### 4. **Maintainability** + +- Clear separation between layers +- Easy to locate and modify functionality +- Consistent coding standards + +## Features + +- **Add Session**: Record a new coding session with start and end times +- **View Sessions**: Display all recorded coding sessions +- **Delete Session**: Remove a specific coding session +- **Persistent Storage**: All data is saved to SQLite database +- **Duration Calculation**: Automatic calculation of session duration + +## Getting Started + +### Prerequisites + +- .NET 10.0 SDK or later +- Windows, Linux, or macOS + +### Installation + +1. Clone the repository +2. Navigate to the project directory +3. Restore dependencies: + ```bash + dotnet restore + ``` + +### Running the Application + +```bash +dotnet run +``` + +## Future Enhancement Considerations + +- Add filtering and sorting capabilities +- Generate statistics and reports on coding habits +- Implement unit tests for validation logic +- Add persistent user sessions +- Create a web API wrapper for the console application +- Implement data export functionality (CSV, JSON) + +## Conclusion + +CodingTracker demonstrates clean architecture principles combined with practical .NET development patterns with the guidance of The C# Academy. The layered structure, clear separation of concerns, and use of proven libraries create a maintainable, scalable foundation for a session tracking application.