diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3bf394b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 + +[*.cs] +wrap_before_binary_opsign = true +wrap_chained_binary_expressions = chop_if_long diff --git a/.gitignore b/.gitignore index 0759950..4c50e11 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ riderModule.iml /_ReSharper.Caches/ .idea/ *~ +*.user diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2f5b08c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/TimeTracker.Server/bin/Debug/net7.0/TimeTracker.Server.dll", + "args": [], + "cwd": "${workspaceFolder}/TimeTracker.Server", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + // "pattern": "\\bNow listening on:\\s+(http?://\\S+)" + "pattern": "\\bgoto\\s+(http://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..fcb23ca --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/TimeTracker.Server/TimeTracker.Server.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/TimeTracker.Server/TimeTracker.Server.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/TimeTracker.Server/TimeTracker.Server.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..f6bd683 --- /dev/null +++ b/TODO.md @@ -0,0 +1,10 @@ +- [ ] create separate TimeTracker.Core with common classes +- [ ] create interface with API methods +- [ ] test it with Server running on localhost +- [ ] authorization with OAuth2 + +Expected API: +- `/categories` - get list of all categories +- `/categores/update` - set new list of categories - it should not be very big +- `/entries` - get a page of entries since provided DateTime +- `/entries/update` - update provided entry diff --git a/TimeTracker/Database/Constants.cs b/TimeTracker.Core/Database/Constants.cs similarity index 87% rename from TimeTracker/Database/Constants.cs rename to TimeTracker.Core/Database/Constants.cs index bfadf80..3d92011 100644 --- a/TimeTracker/Database/Constants.cs +++ b/TimeTracker.Core/Database/Constants.cs @@ -4,7 +4,7 @@ namespace TimeTracker; public static class Constants { - public const string DatabasePath = "tracker.db"; + public const string DatabaseName = "tracker.db"; public const SQLiteOpenFlags Flags = // open the database in read/write mode @@ -13,4 +13,4 @@ public static class Constants SQLiteOpenFlags.Create | // enable multi-threaded database access SQLiteOpenFlags.SharedCache; -} \ No newline at end of file +} diff --git a/TimeTracker/Database/ITable.cs b/TimeTracker.Core/Database/ITable.cs similarity index 100% rename from TimeTracker/Database/ITable.cs rename to TimeTracker.Core/Database/ITable.cs diff --git a/TimeTracker/Database/Migrations/IDbMigration.cs b/TimeTracker.Core/Database/Migrations/IDbMigration.cs similarity index 100% rename from TimeTracker/Database/Migrations/IDbMigration.cs rename to TimeTracker.Core/Database/Migrations/IDbMigration.cs diff --git a/TimeTracker.Core/Database/Migrations/M005_AddTimestampAndReworkElapsed.cs b/TimeTracker.Core/Database/Migrations/M005_AddTimestampAndReworkElapsed.cs new file mode 100644 index 0000000..bf3b305 --- /dev/null +++ b/TimeTracker.Core/Database/Migrations/M005_AddTimestampAndReworkElapsed.cs @@ -0,0 +1,79 @@ +using SQLite; + +namespace TimeTracker.Database.Migrations; + +public class M005_AddTimestampAndReworkElapsed : IDbMigration +{ + public async Task Do(SQLiteAsyncConnection db) + { + var timestamp = DateTime.Now; + + await db.ExecuteAsync("ALTER TABLE TrackedTimeDb ADD Timestamp datetime"); + await db.ExecuteAsync("ALTER TABLE TrackedTimeDb ADD EndTime datetime"); + + var all = await db.Table().ToListAsync(); + foreach (var trackedTimeDb in all) + { + trackedTimeDb.Timestamp = timestamp; +#pragma warning disable CS0618 + trackedTimeDb.EndTime = trackedTimeDb.StartTime + trackedTimeDb.ElapsedTime; +#pragma warning restore CS0618 + await db.UpdateAsync(trackedTimeDb); + } + + // NOTE: sqlite does not support DROP COLUMN + // await db.ExecuteAsync("ALTER TABLE TrackedTimeDb DROP COLUMN ElapsedTime"); + + const string TemporaryTable = "t1_backup"; + await db.ExecuteAsync( + $"CREATE TABLE {TemporaryTable} AS SELECT Id, Uuid, Name, Status, StartTime, EndTime, Timestamp FROM TrackedTimeDb"); + await db.ExecuteAsync("DROP TABLE TrackedTimeDb"); + await db.ExecuteAsync($"ALTER TABLE {TemporaryTable} RENAME TO TrackedTimeDb"); + } + + public Task UnDo(SQLiteAsyncConnection db) + { + throw new NotImplementedException(); + } + + public string Serialize() + { + throw new NotImplementedException(); + } +} + +/// +/// Data model at the time of migration +/// +[Table(nameof(TrackedTimeDb))] +public class TrackedTimeDb_M005 : ITable +{ + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public Guid Uuid { get; set; } + + public string Name { get; set; } + + public DateTime StartTime { get; set; } + + public TimeSpan ElapsedTime { get; init; } + public DateTime EndTime { get; set; } + + public int Status { get; set; } + + /// + /// Last modification time - considering Start and Elapsed may ba changed later + /// + public DateTime Timestamp { get; set; } + + public enum TrackingStatus + { + Completed = 0, + + /// + /// Currently running + /// + Running = 1, + } +} diff --git a/TimeTracker/Database/Migrations/M1_InitializeDb.cs b/TimeTracker.Core/Database/Migrations/M1_InitializeDb.cs similarity index 100% rename from TimeTracker/Database/Migrations/M1_InitializeDb.cs rename to TimeTracker.Core/Database/Migrations/M1_InitializeDb.cs diff --git a/TimeTracker/Database/Migrations/M2_AddCategories.cs b/TimeTracker.Core/Database/Migrations/M2_AddCategories.cs similarity index 100% rename from TimeTracker/Database/Migrations/M2_AddCategories.cs rename to TimeTracker.Core/Database/Migrations/M2_AddCategories.cs diff --git a/TimeTracker/Database/Migrations/M3_AddStatuses.cs b/TimeTracker.Core/Database/Migrations/M3_AddStatuses.cs similarity index 100% rename from TimeTracker/Database/Migrations/M3_AddStatuses.cs rename to TimeTracker.Core/Database/Migrations/M3_AddStatuses.cs diff --git a/TimeTracker.Core/Database/Migrations/M4_AddUuid.cs b/TimeTracker.Core/Database/Migrations/M4_AddUuid.cs new file mode 100644 index 0000000..334909d --- /dev/null +++ b/TimeTracker.Core/Database/Migrations/M4_AddUuid.cs @@ -0,0 +1,58 @@ +using SQLite; + +namespace TimeTracker.Database.Migrations; + +public class M4_AddUuid : IDbMigration +{ + public async Task Do(SQLiteAsyncConnection db) + { + await db.ExecuteAsync("ALTER TABLE TrackedTimeDb ADD Uuid text"); + + var all = await db.Table().ToListAsync(); + foreach (var trackedTimeDb in all) + { + trackedTimeDb.Uuid = Guid.NewGuid(); + await db.UpdateAsync(trackedTimeDb); + } + } + + public Task UnDo(SQLiteAsyncConnection db) + { + throw new NotImplementedException(); + } + + public string Serialize() + { + throw new NotImplementedException(); + } +} + +/// +/// Data model at the time of migration +/// +[Table(nameof(TrackedTimeDb))] +public class TrackedTimeDb_M004 : ITable +{ + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public Guid Uuid { get; set; } + + public string Name { get; set; } + + public DateTime StartTime { get; set; } + + public TimeSpan ElapsedTime { get; init; } + + public int Status { get; set; } + + public enum TrackingStatus + { + Completed = 0, + + /// + /// Currently running + /// + Running = 1, + } +} diff --git a/TimeTracker/Database/Migrator.cs b/TimeTracker.Core/Database/Migrator.cs similarity index 76% rename from TimeTracker/Database/Migrator.cs rename to TimeTracker.Core/Database/Migrator.cs index e2e632a..b2501d6 100644 --- a/TimeTracker/Database/Migrator.cs +++ b/TimeTracker.Core/Database/Migrator.cs @@ -17,6 +17,8 @@ public static class Migrator new M1_InitializeDb(), new M2_AddCategories(), new M3_AddStatuses(), + new M4_AddUuid(), + new M005_AddTimestampAndReworkElapsed(), }; public static async Task Migrate(SQLiteAsyncConnection db) @@ -36,7 +38,15 @@ public static async Task Migrate(SQLiteAsyncConnection db) { var migration = Migrations[i]; - await migration.Do(db); + try + { + await migration.Do(db); + } + catch (Exception e) + { + Debug.WriteLine($"Migration {migration.GetType().Name} failed: {e}"); + throw; + } await db.UpdateAsync(new ControlDb(ControlDb.ParamId.Version, i + 1)); } } diff --git a/TimeTracker.Core/Database/Tables/CategoryDb.cs b/TimeTracker.Core/Database/Tables/CategoryDb.cs new file mode 100644 index 0000000..9956525 --- /dev/null +++ b/TimeTracker.Core/Database/Tables/CategoryDb.cs @@ -0,0 +1,65 @@ +using System.Text.Json.Serialization; +using Microsoft.Maui.Graphics; +using SQLite; + +namespace TimeTracker; + +public class CategoryDb : ITable +{ + public enum CategoryState + { + Enabled = 0, + Disabled = 1, + } + + [PrimaryKey, AutoIncrement, JsonIgnore] public int Id { get; set; } + + public int State { get; set; } + + [Ignore, JsonIgnore] + public CategoryState StateEnum + { + get => (CategoryState)State; + set => State = (int)value; + } + + public string Name { get; set; } + + /// + /// Group of categories + /// + public string CategoryGroup { get; set; } + + public string ColorString { get; set; } + + [Ignore, JsonIgnore] + public Color ColorObject + { + get => Color.Parse(ColorString); + init => ColorString = value.ToArgbHex(); + } + + public override string ToString() + { + return $"Category: {Name}"; + } + + public override bool Equals(object? obj) + { + if (obj is not CategoryDb other) + return false; + + return Equals(other); + } + + protected bool Equals(CategoryDb other) + { + // time trackers reference only category name, so we can compare only by name + return Name == other.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, CategoryGroup, State, ColorString); + } +} diff --git a/TimeTracker/Database/Tables/ControlDb.cs b/TimeTracker.Core/Database/Tables/ControlDb.cs similarity index 92% rename from TimeTracker/Database/Tables/ControlDb.cs rename to TimeTracker.Core/Database/Tables/ControlDb.cs index ba33258..9a507c7 100644 --- a/TimeTracker/Database/Tables/ControlDb.cs +++ b/TimeTracker.Core/Database/Tables/ControlDb.cs @@ -2,6 +2,9 @@ namespace TimeTracker; +/// +/// Table to store control parameters. +/// public class ControlDb : ITable { public enum ParamId diff --git a/TimeTracker/Database/Tables/TrackedTimeDb.cs b/TimeTracker.Core/Database/Tables/TrackedTimeDb.cs similarity index 62% rename from TimeTracker/Database/Tables/TrackedTimeDb.cs rename to TimeTracker.Core/Database/Tables/TrackedTimeDb.cs index 6ab5bfe..d12cd29 100644 --- a/TimeTracker/Database/Tables/TrackedTimeDb.cs +++ b/TimeTracker.Core/Database/Tables/TrackedTimeDb.cs @@ -2,19 +2,31 @@ namespace TimeTracker; +/// +/// +/// public class TrackedTimeDb : ITable { [PrimaryKey, AutoIncrement] public int Id { get; set; } + public Guid Uuid { get; set; } + public string Name { get; set; } public DateTime StartTime { get; set; } - public TimeSpan ElapsedTime { get; init; } + public DateTime EndTime { get; set; } + + [Ignore] public TimeSpan ElapsedTime => EndTime - StartTime; public int Status { get; set; } + /// + /// Last modification time - considering Start and Elapsed may ba changed later + /// + public DateTime Timestamp { get; set; } + [Ignore] public TrackingStatus StatusEnum { diff --git a/TimeTracker/Database/TrackerDatabase.cs b/TimeTracker.Core/Database/TrackerDatabase.cs similarity index 75% rename from TimeTracker/Database/TrackerDatabase.cs rename to TimeTracker.Core/Database/TrackerDatabase.cs index 1bab219..5bededd 100644 --- a/TimeTracker/Database/TrackerDatabase.cs +++ b/TimeTracker.Core/Database/TrackerDatabase.cs @@ -1,20 +1,27 @@ -using SQLite; +using Microsoft.Maui.Graphics; +using SQLite; namespace TimeTracker; public class TrackerDatabase { private readonly SQLiteAsyncConnection _database; + private static string _databaseName = Constants.DatabaseName; - private TrackerDatabase() + private TrackerDatabase(string databaseName) { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - var path = Path.Combine(home, Constants.DatabasePath); + var path = Path.Combine(home, databaseName); _database = new SQLiteAsyncConnection(path, Constants.Flags); } - public static readonly AsyncLazy Instance = new(async () => await new TrackerDatabase().InitializeDb()); + public static readonly AsyncLazy Instance = new(async () => await new TrackerDatabase(_databaseName).InitializeDb()); + + public static void InitializePath(string databaseName) + { + _databaseName = databaseName; + } public Task InsertAsync(T result) where T : ITable, new() { @@ -51,27 +58,27 @@ public Task> GetCategories() internal static Task> DefaultCategories() { - return Task.FromResult(new List() + return Task.FromResult(new List { - new CategoryDb() + new CategoryDb { Name = "Code", ColorObject = Colors.DarkSlateBlue, CategoryGroup = Work, }, - new CategoryDb() + new CategoryDb { Name = "Tasks", ColorObject = Colors.DarkCyan, CategoryGroup = Work, }, - new CategoryDb() + new CategoryDb { Name = "Call", ColorObject = Colors.DarkGreen, CategoryGroup = Work, }, - new CategoryDb() + new CategoryDb { Name = "Review", ColorObject = Colors.DarkSeaGreen, @@ -83,43 +90,43 @@ internal static Task> DefaultCategories() ColorObject = Colors.DarkSeaGreen, CategoryGroup = Work, }, - new CategoryDb() + new CategoryDb { Name = "Pet", ColorObject = Colors.DarkKhaki, CategoryGroup = Work, }, - new CategoryDb() + new CategoryDb { Name = "Sport", ColorObject = Colors.LightGray, CategoryGroup = Personal, }, - new CategoryDb() + new CategoryDb { Name = "Game", ColorObject = Colors.DarkOrchid, CategoryGroup = Personal, }, - new CategoryDb() + new CategoryDb { Name = "Eat", ColorObject = Colors.DarkViolet, CategoryGroup = Personal, }, - new CategoryDb() + new CategoryDb { Name = "Leisure", ColorObject = Colors.DarkGoldenrod, CategoryGroup = Personal, }, - new CategoryDb() + new CategoryDb { Name = "Art", ColorObject = Colors.DarkRed, CategoryGroup = Personal, }, - new CategoryDb() + new CategoryDb { Name = "Family", ColorObject = Colors.DarkOliveGreen, @@ -161,6 +168,28 @@ public async Task Update(TrackedTimeDb tracker) } } + public async Task Update(CategoryDb category) + { + await _dbSemaphore.WaitAsync(); + + try + { + if (category.Id == 0) + { + await _database.InsertAsync(category); + category.Id = category.Id; + } + else + { + await _database.UpdateAsync(category); + } + } + finally + { + _dbSemaphore.Release(); + } + } + public async Task> ListRunningTrackers() { await _dbSemaphore.WaitAsync(); @@ -181,7 +210,7 @@ public async Task> ListRunningTrackers() } } - public async Task Remove(TrackedTimeDb tracker) + public async Task RemoveAsync(TrackedTimeDb tracker) { await _dbSemaphore.WaitAsync(); @@ -194,4 +223,18 @@ public async Task Remove(TrackedTimeDb tracker) _dbSemaphore.Release(); } } + + public async Task RemoveAsync(CategoryDb category) + { + await _dbSemaphore.WaitAsync(); + + try + { + await _database.DeleteAsync(category); + } + finally + { + _dbSemaphore.Release(); + } + } } diff --git a/TimeTracker/Helpers/AsyncLazy.cs b/TimeTracker.Core/Helpers/AsyncLazy.cs similarity index 100% rename from TimeTracker/Helpers/AsyncLazy.cs rename to TimeTracker.Core/Helpers/AsyncLazy.cs diff --git a/TimeTracker/Helpers/AsyncUtil.cs b/TimeTracker.Core/Helpers/AsyncUtil.cs similarity index 100% rename from TimeTracker/Helpers/AsyncUtil.cs rename to TimeTracker.Core/Helpers/AsyncUtil.cs diff --git a/TimeTracker/Models/TimeTracker.cs b/TimeTracker.Core/Models/TimeTracker.cs similarity index 92% rename from TimeTracker/Models/TimeTracker.cs rename to TimeTracker.Core/Models/TimeTracker.cs index c226be4..f8d4bea 100644 --- a/TimeTracker/Models/TimeTracker.cs +++ b/TimeTracker.Core/Models/TimeTracker.cs @@ -30,7 +30,7 @@ public TimeTracker(TrackedTimeDb tracker) StartTime = tracker.StartTime; IsRunning = tracker.StatusEnum == TrackedTimeDb.TrackingStatus.Running; if (IsRunning == false) - EndTime = tracker.StartTime + tracker.ElapsedTime; + EndTime = tracker.EndTime; } public TimeTracker Start() @@ -53,9 +53,10 @@ public TrackedTimeDb ToDb() return new TrackedTimeDb { Id = Id, + Uuid = new Guid(), Name = Name, StartTime = StartTime, - ElapsedTime = ElapsedTime, + EndTime = EndTime, StatusEnum = IsRunning ? TrackedTimeDb.TrackingStatus.Running : TrackedTimeDb.TrackingStatus.Completed, diff --git a/TimeTracker.Core/TimeTracker.Core.csproj b/TimeTracker.Core/TimeTracker.Core.csproj new file mode 100644 index 0000000..af093fc --- /dev/null +++ b/TimeTracker.Core/TimeTracker.Core.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + TimeTracker + + + + + + + diff --git a/TimeTracker.Server/Controllers/CategoriesController.cs b/TimeTracker.Server/Controllers/CategoriesController.cs new file mode 100644 index 0000000..6783f3e --- /dev/null +++ b/TimeTracker.Server/Controllers/CategoriesController.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; + +namespace TimeTracker.Server.Controllers; + +[Route("[controller]")] +public class CategoriesController : ControllerBase +{ + private readonly ILogger _logger; + + public CategoriesController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(template: "get", Name = "GetAllCategories")] + public async Task> Get() + { + var db = await TrackerDatabase.Instance; + var categories = await db.GetCategories(); + + return categories; + } + + [HttpPost(template: "update", Name = "UpdateCategories")] + public async Task UpdateCategories([FromBody] List newCategories) + { + var db = await TrackerDatabase.Instance; + var oldCategories = await db.GetCategories(); + + var added = newCategories.Except(oldCategories); + var removed = oldCategories.Except(newCategories); + var modified = newCategories.Intersect(oldCategories); + + foreach (var category in added) + { + await db.InsertAsync(category); + } + + foreach (var category in removed) + { + await db.RemoveAsync(category); + } + + foreach (var category in modified) + { + await db.Update(category); + } + + return Ok(); + } +} diff --git a/TimeTracker.Server/Program.cs b/TimeTracker.Server/Program.cs new file mode 100644 index 0000000..6bfd5b6 --- /dev/null +++ b/TimeTracker.Server/Program.cs @@ -0,0 +1,31 @@ +using TimeTracker.Server; + +var builder = WebApplication.CreateBuilder(args); + +TimeTracker.TrackerDatabase.InitializePath(WebConstants.WebDatabaseName); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +Console.WriteLine("goto http://localhost:5007/WeatherForecast"); + +app.Run(); diff --git a/TimeTracker.Server/Properties/launchSettings.json b/TimeTracker.Server/Properties/launchSettings.json new file mode 100644 index 0000000..5d16db3 --- /dev/null +++ b/TimeTracker.Server/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50925", + "sslPort": 44394 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5007", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7191;http://localhost:5007", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/TimeTracker.Server/TimeTracker.Server.csproj b/TimeTracker.Server/TimeTracker.Server.csproj new file mode 100644 index 0000000..8ef5b38 --- /dev/null +++ b/TimeTracker.Server/TimeTracker.Server.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + diff --git a/TimeTracker.Server/WeatherForecast.cs b/TimeTracker.Server/WeatherForecast.cs new file mode 100644 index 0000000..c52be2d --- /dev/null +++ b/TimeTracker.Server/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace TimeTracker.Server; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/TimeTracker.Server/WebConstants.cs b/TimeTracker.Server/WebConstants.cs new file mode 100644 index 0000000..607e0d5 --- /dev/null +++ b/TimeTracker.Server/WebConstants.cs @@ -0,0 +1,6 @@ +namespace TimeTracker.Server; + +public static class WebConstants +{ + public const string WebDatabaseName = "web.tracker.db"; +} diff --git a/TimeTracker.Server/appsettings.Development.json b/TimeTracker.Server/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/TimeTracker.Server/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/TimeTracker.Server/appsettings.json b/TimeTracker.Server/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/TimeTracker.Server/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/TimeTracker.Tests/CategoriesTest.cs b/TimeTracker.Tests/CategoriesTest.cs new file mode 100644 index 0000000..a7ebbff --- /dev/null +++ b/TimeTracker.Tests/CategoriesTest.cs @@ -0,0 +1,75 @@ +using System.Diagnostics; +using Microsoft.Maui.Graphics; + +namespace TimeTracker.Tests; + +public class CategoriesTest +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void TestIfCategoriesEquals() + { + var cat1 = new CategoryDb + { + Id = 1, + Name = "Review", + CategoryGroup = "Work", + StateEnum = CategoryDb.CategoryState.Enabled, + ColorObject = Colors.Aqua, + }; + + var cat2 = new CategoryDb + { + Id = 2, + Name = "Review", + CategoryGroup = "Work", + StateEnum = CategoryDb.CategoryState.Enabled, + ColorObject = Colors.Aqua, + }; + + Assert.That(cat1, Is.EqualTo(cat2)); + } + + [Test] + public void TestIfCategoriesList() + { + var cat1 = new CategoryDb + { + Id = 1, + Name = "Task1", + CategoryGroup = "Work", + StateEnum = CategoryDb.CategoryState.Enabled, + ColorObject = Colors.Aqua, + }; + + var cat2 = new CategoryDb + { + Id = 2, + Name = "Task2", + CategoryGroup = "Work", + StateEnum = CategoryDb.CategoryState.Enabled, + ColorObject = Colors.Beige, + }; + + var cat3 = new CategoryDb + { + Id = 3, + Name = "Task3", + CategoryGroup = "Personal", + StateEnum = CategoryDb.CategoryState.Enabled, + ColorObject = Colors.Chocolate, + }; + + var list1 = new List { cat1, cat2 }; + var list2 = new List { cat2, cat3 }; + + var intersection = list1.Intersect(list2).ToList(); + + Assert.That(intersection, Has.Count.EqualTo(1)); + CollectionAssert.Contains(intersection, cat2); + } +} diff --git a/TimeTracker.Tests/TimeTracker.Tests.csproj b/TimeTracker.Tests/TimeTracker.Tests.csproj new file mode 100644 index 0000000..62d720c --- /dev/null +++ b/TimeTracker.Tests/TimeTracker.Tests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + diff --git a/TimeTracker.Tests/Usings.cs b/TimeTracker.Tests/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/TimeTracker.Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/TimeTracker.sln b/TimeTracker.sln index 27dc4af..39ee23d 100644 --- a/TimeTracker.sln +++ b/TimeTracker.sln @@ -1,7 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeTracker", "TimeTracker\TimeTracker.csproj", "{9B5B958D-5ABE-42CA-AE57-1EDF19DDE07D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeTracker.Server", "TimeTracker.Server\TimeTracker.Server.csproj", "{9D532BBD-5895-461F-AC46-C78A22801F11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeTracker.Core", "TimeTracker.Core\TimeTracker.Core.csproj", "{E46CD9A9-210A-4F7A-9F53-E8AAD80BA919}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeTracker.Tests", "TimeTracker.Tests\TimeTracker.Tests.csproj", "{C8D7C701-1AEE-4CAE-AAAB-EFCE377D9271}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +19,17 @@ Global {9B5B958D-5ABE-42CA-AE57-1EDF19DDE07D}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B5B958D-5ABE-42CA-AE57-1EDF19DDE07D}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B5B958D-5ABE-42CA-AE57-1EDF19DDE07D}.Release|Any CPU.Build.0 = Release|Any CPU + {9D532BBD-5895-461F-AC46-C78A22801F11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D532BBD-5895-461F-AC46-C78A22801F11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D532BBD-5895-461F-AC46-C78A22801F11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D532BBD-5895-461F-AC46-C78A22801F11}.Release|Any CPU.Build.0 = Release|Any CPU + {E46CD9A9-210A-4F7A-9F53-E8AAD80BA919}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E46CD9A9-210A-4F7A-9F53-E8AAD80BA919}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E46CD9A9-210A-4F7A-9F53-E8AAD80BA919}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E46CD9A9-210A-4F7A-9F53-E8AAD80BA919}.Release|Any CPU.Build.0 = Release|Any CPU + {C8D7C701-1AEE-4CAE-AAAB-EFCE377D9271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8D7C701-1AEE-4CAE-AAAB-EFCE377D9271}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8D7C701-1AEE-4CAE-AAAB-EFCE377D9271}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8D7C701-1AEE-4CAE-AAAB-EFCE377D9271}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/TimeTracker/Database/Tables/CategoryDb.cs b/TimeTracker/Database/Tables/CategoryDb.cs deleted file mode 100644 index 34a6e52..0000000 --- a/TimeTracker/Database/Tables/CategoryDb.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.Maui.Graphics.Text; -using SQLite; - -namespace TimeTracker; - -public class CategoryDb : ITable -{ - public enum CategoryState - { - Enabled = 0, - Disabled = 1, - } - - [PrimaryKey, AutoIncrement] - public int Id { get; set; } - - public int State { get; set; } - - [Ignore] - public CategoryState StateEnum - { - get => (CategoryState) State; - set => State = (int) value; - } - - public string Name { get; set; } - - /// - /// Group of categories - /// - public string CategoryGroup { get; set; } - - public string ColorString { get; set; } - - [Ignore] - public Color ColorObject - { - get => Color.Parse(ColorString); - set => ColorString = value.ToArgbHex(); - } - - public override string ToString() - { - return $"Category: {Name}"; - } -} diff --git a/TimeTracker/TimeTracker.csproj b/TimeTracker/TimeTracker.csproj index a6c0f8d..b972c94 100644 --- a/TimeTracker/TimeTracker.csproj +++ b/TimeTracker/TimeTracker.csproj @@ -52,7 +52,6 @@ - @@ -89,4 +88,8 @@ + + + + diff --git a/TimeTracker/WebRequests.cs b/TimeTracker/WebRequests.cs new file mode 100644 index 0000000..610d4da --- /dev/null +++ b/TimeTracker/WebRequests.cs @@ -0,0 +1,13 @@ +namespace TimeTracker; + +/// +/// Performs web requests to the TimeTracker API. +/// +public class WebRequests +{ + public void Synchronize() + { + // 1 download all data from server + // 2 push all uncynced data to server + } +}