### Install Linux Dependencies Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Command to install required WebKitGTK libraries for Linux. ```bash sudo apt-get install libwebkit2gtk-4.1 ``` -------------------------------- ### Install Snapcraft and Build Dependencies Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands to install Snapcraft and the LXD build provider on Ubuntu. ```bash sudo snap install snapcraft --classic ``` ```bash sudo snap install lxd ``` ```bash sudo lxd init --auto ``` ```bash sudo usermod -aG lxd $USER ``` ```bash newgrp lxd ``` -------------------------------- ### Install Flatpak and Builder Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands to set up the Flatpak environment and build tools on Ubuntu. ```bash sudo apt install flatpak ``` ```bash sudo apt install gnome-software-plugin-flatpak ``` ```bash flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo ``` ```bash flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo ``` ```bash sudo apt install flatpak-builder ``` ```bash flatpak-builder --version ``` ```bash sudo add-apt-repository ppa:flatpak/development sudo apt update sudo apt install flatpak-builder ``` -------------------------------- ### Build and Manage Snap Packages Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands for building, cleaning, installing, and uploading Snap packages. ```bash SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=1 snapcraft pack --debug ``` ```bash snapcraft clean openhabittracker ``` ```bash sudo snap install openhabittracker_1.1.7_amd64.snap --dangerous --devmode ``` ```bash snap list ``` ```bash snap run openhabittracker ``` ```bash snapcraft login ``` ```bash snapcraft upload --release=stable openhabittracker_1.1.7_amd64.snap ``` ```bash snapcraft status openhabittracker ``` -------------------------------- ### Docker Compose Configuration Source: https://github.com/jinjinov/openhabittracker/blob/main/README.md Example docker-compose.yml file for setting up and running OpenHabitTracker. ```APIDOC ## Docker Compose Configuration ### Description This `docker-compose.yml` file demonstrates how to set up and run the OpenHabitTracker service using a Docker image. It includes environment variable configuration for user credentials and JWT secrets, as well as volume mapping for persistent data. ### File: `docker-compose.yml` ```yaml services: openhabittracker: image: jinjinov/openhabittracker:latest ports: - "5000:8080" environment: - AppSettings__UserName=${APPSETTINGS_USERNAME} - AppSettings__Email=${APPSETTINGS_EMAIL} - AppSettings__Password=${APPSETTINGS_PASSWORD} - AppSettings__JwtSecret=${APPSETTINGS_JWT_SECRET} volumes: - ./.OpenHabitTracker:/app/.OpenHabitTracker ``` ### Environment Variables Before running the Docker Compose file, ensure you have a `.env` file or set the following environment variables: - `APPSETTINGS_USERNAME`: The username for logging into the application. - `APPSETTINGS_EMAIL`: The email address for the user. - `APPSETTINGS_PASSWORD`: The password for the user. - `APPSETTINGS_JWT_SECRET`: A strong, randomly generated secret key for JWT authentication. **Generating JWT Secret:** **Windows:** ```powershell [System.Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)) ``` **Linux / macOS:** ```bash openssl rand -base64 32 ``` ### Usage 1. Save the content above as `docker-compose.yml`. 2. Create a `.env` file in the same directory with your desired environment variables. 3. Run the command: `docker-compose up -d` After the container is running, you can access the application at `http://localhost:5000` and log in using the credentials defined in your environment variables. ``` -------------------------------- ### Run flatpak-builder-lint Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Installs and runs the flatpak-builder-lint tool to check the application manifest and repository. ```bash flatpak install flathub -y org.flatpak.Builder flatpak run --command=flatpak-builder-lint org.flatpak.Builder --help ``` ```bash flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest net.openhabittracker.OpenHabitTracker.yaml ``` ```bash flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo ``` -------------------------------- ### Start Docker Container Source: https://context7.com/jinjinov/openhabittracker/llms.txt Command to start the OpenHabitTracker Docker container in detached mode. ```bash # Start the container docker compose up -d ``` -------------------------------- ### Using Target-Typed new() in C# Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.md Example demonstrating the use of target-typed `new()` for object instantiation in C#. This convention aims for more concise code. ```csharp List items = new(); ``` -------------------------------- ### Build and Publish macOS MAUI App Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands for running on macOS and generating the PKG installer. ```bash dotnet build OpenHabitTracker.Blazor.Maui.csproj -t:Run -c:Release -f:net9.0-maccatalyst ``` ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-maccatalyst -p:MtouchLink=SdkOnly -p:CreatePackage=true -p:EnableCodeSigning=true -p:EnablePackageSigning=true -p:CodesignKey="Apple Distribution: Urban Dzindzinovic (53V66WG4KU)" -p:CodesignProvision="openhabittracker.macos" -p:CodesignEntitlements="Platforms\MacCatalyst\Entitlements.plist" -p:PackageSigningKey="3rd Party Mac Developer Installer: Urban Dzindzinovic (53V66WG4KU)" ``` -------------------------------- ### Register Core Services for Dependency Injection Source: https://context7.com/jinjinov/openhabittracker/llms.txt Configures core and business logic services for dependency injection. Includes localization and Markdown processing setup. ```csharp public static class Startup { public static IServiceCollection AddServices(this IServiceCollection services) { // Core state and utilities services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Business logic services services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Localization (20 languages) services.AddLocalization(options => options.ResourcesPath = @"Localization\Resources"); services.AddSingleton(); // Markdown processing with Markdig services.AddSingleton( new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseSoftlineBreakAsHardlineBreak() .Build()); return services; } } ``` ```csharp // Example: Entry point setup (Blazor WASM) WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args); builder.Services.AddServices(); // Core services builder.Services.AddDataAccess(); // IndexedDB for browser builder.Services.AddBackup(); // Import/export services builder.Services.AddBlazor(); // Blazor-specific services // Platform-specific registrations builder.Services.AddScoped(); builder.Services.AddScoped(); ``` -------------------------------- ### Documenting Test Prerequisites Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.EndToEndTests/TODO.md Include this comment at the top of test fixtures to remind developers to start the application server before executing tests. ```csharp // Prerequisite: start OpenHabitTracker.Blazor.Web at http://localhost before running tests. // dotnet run --project OpenHabitTracker.Blazor.Web --configuration Release ``` -------------------------------- ### Build and Push Docker Containers Source: https://github.com/jinjinov/openhabittracker/blob/main/Release.md Commands to build and push the web application container images. ```bash docker compose build push to Docker Hub push to GitHub Container Registry ``` -------------------------------- ### .NET CLI Workload and Environment Commands Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Common commands for managing .NET environment information and workloads. ```bash dotnet --info ``` ```bash dotnet workload search ``` ```bash dotnet workload list ``` ```bash dotnet workload update ``` ```bash dotnet nuget locals all --list ``` ```bash dotnet nuget locals all --clear ``` -------------------------------- ### Configure Snapcraft Extensions Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Add the required extensions to the snapcraft.yaml file to support .NET 9. ```yaml extensions: [ gnome, dotnet9 ] ``` -------------------------------- ### Build and Validate Flatpak Packages Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands to generate dependencies, validate metadata, and build the Flatpak application. ```bash flatpak-builder build-dir --user --install-deps-from=flathub --download-only net.openhabittracker.OpenHabitTracker.yaml --force-clean ``` ```bash python3 flatpak-dotnet-generator.py --dotnet 9 --freedesktop 25.08 nuget-sources.json OpenHabitTracker/OpenHabitTracker.Blazor.Photino/OpenHabitTracker.Blazor.Photino.csproj ``` ```bash desktop-file-validate net.openhabittracker.OpenHabitTracker.desktop ``` ```bash sudo apt install appstream-util appstream-util validate-relax net.openhabittracker.OpenHabitTracker.metainfo.xml appstream-util validate net.openhabittracker.OpenHabitTracker.metainfo.xml ``` ```bash flatpak install -y flathub org.flatpak.Builder ``` ```bash flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream net.openhabittracker.OpenHabitTracker.metainfo.xml ``` ```bash flatpak-builder build-dir --user --force-clean --install --repo=repo net.openhabittracker.OpenHabitTracker.yaml ``` ```bash flatpak-builder build-dir --user --force-clean --install --repo=repo net.openhabittracker.OpenHabitTracker.yaml --disable-rofiles-fuse ``` ```bash flatpak run net.openhabittracker.OpenHabitTracker ``` -------------------------------- ### Publish OpenHabitTracker MAUI for Windows Source: https://github.com/jinjinov/openhabittracker/blob/main/Release.md Publishes the Windows application package for the Microsoft Store. ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-windows10.0.19041.0 -p:SelfContained=true -p:PublishAppxPackage=true ``` -------------------------------- ### Get External Storage Path for Android Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md Requests WRITE_EXTERNAL_STORAGE permission if not granted and returns a path for saving files in the Documents directory. Ensure MainActivity has a static Instance property for ActivityCompat.RequestPermissions to work correctly. ```csharp using System; using System.IO; using System.Runtime.InteropServices; using Android.Content.PM; using Android.OS; using Xamarin.Essentials; using Android; using Android.Content.PM; using Android.Support.V4.App; using Android.Support.V4.Content; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // LocalApplicationData, ApplicationData, UserProfile, Personal, MyDocuments, Desktop, DesktopDirectory { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) // LocalApplicationData, ApplicationData, UserProfile, Personal, MyDocuments, Desktop, DesktopDirectory { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Android)) { if (ContextCompat.CheckSelfPermission(Android.App.Application.Context, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted) { ActivityCompat.RequestPermissions(MainActivity.Instance, new string[] { Manifest.Permission.WriteExternalStorage }, 1); } path = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "MyAppFolder"); return Path.Combine(Android.OS.Environment.ExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments).AbsolutePath, "MyAppFolder"); } if (RuntimeInformation.IsOSPlatform(OSPlatform.iOS)) { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } ``` -------------------------------- ### Disable EF Core SQLite Connection Pooling Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Example connection string to disable connection pooling for SQLite in EF Core. This forces new connections for each DbContext, ensuring immediate visibility of committed changes and faster lock release. ```csharp "Data Source=mydb.db;Pooling=False" ``` -------------------------------- ### Build Flatpak for OpenHabitTracker Source: https://github.com/jinjinov/openhabittracker/blob/main/Release.md Commands for building the Flatpak package using flatpak-builder and the dotnet generator. ```bash flatpak-builder --download-only python3 flatpak-dotnet-generator.py `nuget-sources.json` flatpak-builder ``` -------------------------------- ### Initialize and Manage Trashed Items with TrashService Source: https://context7.com/jinjinov/openhabittracker/llms.txt Initializes the trash service by loading soft-deleted items. Use for restoring individual items, all items, or permanently deleting them. ```csharp public class TrashService : ITrashService { public IReadOnlyList? TrashedHabits { get; } public IReadOnlyList? TrashedNotes { get; } public IReadOnlyList? TrashedTasks { get; } public async Task Initialize() { await _clientState.LoadTrash(); } } ``` ```csharp // Example: Restore a deleted habit await trashService.Restore(habitModel); ``` ```csharp // Example: Restore a deleted note await trashService.Restore(noteModel); ``` ```csharp // Example: Restore a deleted task await trashService.Restore(taskModel); ``` ```csharp // Example: Restore all trashed items await trashService.RestoreAll(); ``` ```csharp // Example: Permanently delete a specific item await trashService.Delete(habitModel); await trashService.Delete(noteModel); await trashService.Delete(taskModel); ``` ```csharp // Example: Empty entire trash (permanent deletion) await trashService.EmptyTrash(); ``` -------------------------------- ### Configure NuGet Package References Source: https://github.com/jinjinov/openhabittracker/blob/main/CloudStorage.md Required NuGet packages for integrating various OAuth authentication providers into the project. ```xml ``` -------------------------------- ### Publish Windows MAUI App Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands to publish the Windows version of the application. ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-windows10.0.19041.0 -p:SelfContained=true -p:GenerateAppxPackageOnBuild=true ``` ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-windows10.0.19041.0 -p:SelfContained=true -p:PublishAppxPackage=true ``` -------------------------------- ### Build and Publish iOS MAUI App Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands for running on the iOS simulator and publishing the IPA file. ```bash dotnet build OpenHabitTracker.Blazor.Maui.csproj -t:Run -c:Release -f:net9.0-ios ``` ```bash dotnet build OpenHabitTracker.Blazor.Maui.csproj -t:Run -c:Release -f:net9.0-ios -p:_DeviceName=:v2:udid=YOUR_UDID ``` ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-ios -p:ArchiveOnBuild=true -p:RuntimeIdentifier=ios-arm64 -p:CodesignKey="Apple Distribution: Urban Dzindzinovic (53V66WG4KU)" -p:CodesignProvision="openhabittracker.ios" ``` -------------------------------- ### Run Android MAUI App Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Command to launch the application on an Android emulator. ```bash dotnet build -t:Run -f:net9.0-android ``` -------------------------------- ### Configure NuGet Package Sources Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Defines local package sources for the Photino Blazor project. ```xml ``` -------------------------------- ### Configure OAuth and OpenID Connect Providers Source: https://github.com/jinjinov/openhabittracker/blob/main/CloudStorage.md Configures Dropbox and Apple authentication providers, including custom claim mapping and token persistence. ```csharp var identity = context.Principal.Identity as ClaimsIdentity; var email = context.Principal.FindFirst(ClaimTypes.Email)?.Value; var name = context.Principal.FindFirst(ClaimTypes.Name)?.Value; identity.AddClaim(new Claim("email", email ?? string.Empty)); identity.AddClaim(new Claim("name", name ?? string.Empty)); // Save tokens for later API calls var tokens = JsonSerializer.Serialize(context.Properties.GetTokens()); identity.AddClaim(new Claim("tokens", tokens)); }; }) .AddDropbox(options => { options.ClientId = "Your-Dropbox-Client-Id"; options.ClientSecret = "Your-Dropbox-Client-Secret"; options.SaveTokens = true; options.Events.OnCreatingTicket = async context => { var identity = context.Principal.Identity as ClaimsIdentity; // Dropbox doesn't return email in default scopes, so fetch additional data if needed var userInfoResponse = await context.Backchannel.GetAsync("https://api.dropboxapi.com/2/users/get_current_account"); if (userInfoResponse.IsSuccessStatusCode) { var userInfo = JsonDocument.Parse(await userInfoResponse.Content.ReadAsStringAsync()); var email = userInfo.RootElement.GetProperty("email").GetString(); var name = userInfo.RootElement.GetProperty("name").GetProperty("display_name").GetString(); identity.AddClaim(new Claim("email", email ?? string.Empty)); identity.AddClaim(new Claim("name", name ?? string.Empty)); } }; }) .AddOpenIdConnect("iCloud", options => { options.Authority = "https://appleid.apple.com"; options.ClientId = "Your-Apple-Client-Id"; options.ClientSecret = "Your-Apple-Client-Secret"; // Use JWT-based client secret as per Apple guidelines options.ResponseType = "code"; options.Scope.Add("email"); options.Scope.Add("name"); options.SaveTokens = true; options.Events.OnTokenValidated = context => { var identity = context.Principal.Identity as ClaimsIdentity; var email = context.Principal.FindFirst(ClaimTypes.Email)?.Value; var name = context.Principal.FindFirst("name")?.Value; identity.AddClaim(new Claim("email", email ?? string.Empty)); identity.AddClaim(new Claim("name", name ?? string.Empty)); return Task.CompletedTask; }; }); return services; } } ``` -------------------------------- ### HabitService for Habit Lifecycle Operations Source: https://context7.com/jinjinov/openhabittracker/llms.txt HabitService handles habit creation, tracking, completion, and deletion using a soft-delete pattern. Initialize the service and load habits before performing operations. ```csharp public class HabitService : IHabitService { public IReadOnlyCollection? Habits { get; } public HabitModel? SelectedHabit { get; set; } public HabitModel? NewHabit { get; set; } // Initialize service and load habits public async Task Initialize() { await _clientState.LoadCategories(); await _clientState.LoadHabits(); } // Filter habits using query parameters public IEnumerable GetHabits() { QueryParameters queryParameters = _searchFilterService.GetQueryParameters(_clientState.Settings); return Habits!.FilterHabits(queryParameters); } } ``` ```csharp HabitModel newHabit = new() { Title = "Exercise", RepeatCount = 1, RepeatInterval = 1, RepeatPeriod = Period.Day, Priority = Priority.High, CategoryId = categoryId }; habitService.NewHabit = newHabit; await habitService.AddHabit(); ``` ```csharp await habitService.Start(habit); ``` ```csharp await habitService.MarkAsDone(habit); ``` ```csharp await habitService.AddTimeDone(habit, DateTime.Now); ``` ```csharp await habitService.RemoveTimeDone(habit, timeModel); ``` ```csharp await habitService.DeleteHabit(habit); ``` ```csharp await habitService.LoadTimesDone(habit); ``` -------------------------------- ### Blazor Component Instantiation Error Handling Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Exceptions during Blazor component constructor or DI service instantiation are fatal. Use `@using Microsoft.Extensions.Logging` and `@inject ILogger Logger` to mitigate constructor exceptions. ```csharp @using Microsoft.Extensions.Logging @inject ILogger Logger ``` -------------------------------- ### Publish OpenHabitTracker MAUI for Android Source: https://github.com/jinjinov/openhabittracker/blob/main/Release.md Publishes the Android application package. ```bash dotnet publish -c Release -f:net9.0-android ... ``` -------------------------------- ### Add EF Core Migration Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Use this command to add a new migration to your Entity Framework Core project. Specify the startup project if it differs from the migration project. ```bash dotnet ef migrations add Initial --project OpenHabitTracker.EntityFrameworkCore --startup-project OpenHabitTracker.Blazor.WinForms ``` -------------------------------- ### JavaScript ResizeObserver Implementation Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md This JavaScript code sets up a ResizeObserver to monitor element width changes. It includes guards against undefined ResizeObserver, double observation, and tracks the last observed width to trigger callbacks only when the width actually changes. The callback iterates through observed entries and invokes a .NET method via `dotnetRef.invokeMethodAsync`. ```javascript const _resizeObservers = new Map(); export function observeElementWidth(id, element, dotnetRef) { if (typeof ResizeObserver === 'undefined') return; // 3. old iOS/Android guard if (_resizeObservers.has(id)) return; // guard against double-observe let lastWidth = 0; const ro = new ResizeObserver(entries => { for (let entry of entries) { ``` -------------------------------- ### Publish OpenHabitTracker MAUI for Mac Catalyst Source: https://github.com/jinjinov/openhabittracker/blob/main/Release.md Publishes the Mac Catalyst application with code and package signing requirements. ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-maccatalyst -p:MtouchLink=SdkOnly -p:CreatePackage=true -p:EnableCodeSigning=true -p:EnablePackageSigning=true -p:CodesignKey="Apple Distribution: Urban Dzindzinovic (53V66WG4KU)" -p:CodesignProvision="openhabittracker.macos" -p:CodesignEntitlements="Platforms\MacCatalyst\Entitlements.plist" -p:PackageSigningKey="3rd Party Mac Developer Installer: Urban Dzindzinovic (53V66WG4KU)" ``` -------------------------------- ### Docker Compose Configuration for OpenHabitTracker Source: https://github.com/jinjinov/openhabittracker/blob/main/README.md Docker Compose file to set up and run the OpenHabitTracker service, mapping ports and configuring environment variables and volumes. ```yaml services: openhabittracker: image: jinjinov/openhabittracker:latest ports: - "5000:8080" environment: - AppSettings__UserName=${APPSETTINGS_USERNAME} - AppSettings__Email=${APPSETTINGS_EMAIL} - AppSettings__Password=${APPSETTINGS_PASSWORD} - AppSettings__JwtSecret=${APPSETTINGS_JWT_SECRET} volumes: - ./.OpenHabitTracker:/app/.OpenHabitTracker ``` -------------------------------- ### Initialize and Manage Checklist Items with ItemService Source: https://context7.com/jinjinov/openhabittracker/llms.txt Initializes checklist items for a parent task or habit, handling lazy loading and caching. Use for adding, updating, toggling completion, and deleting items. ```csharp public class ItemService : IItemService { public ItemModel? SelectedItem { get; set; } public ItemModel? NewItem { get; set; } // Initialize and load items for a specific parent (task or habit) public async Task Initialize(ItemsModel? items) { if (items is not null && items.Items is null) { IReadOnlyList itms = await _clientState.DataAccess.GetItems(items.Id); items.Items = itms.Select(i => i.ToModel()).ToList(); _clientState.Items ??= new(); foreach (ItemModel item in items.Items) _clientState.Items[item.Id] = item; } NewItem ??= new(); } } ``` ```csharp // Example: Add a checklist item to a task or habit itemService.NewItem = new ItemModel { Title = "Review documentation" }; await itemService.AddItem(taskOrHabit); ``` ```csharp // Example: Toggle item completion await itemService.SetIsDone(item, done: true); ``` ```csharp // Example: Update item title itemService.SelectedItem = item; await itemService.UpdateItem("Updated item title"); ``` ```csharp // Example: Delete an item await itemService.DeleteItem(taskOrHabit, item); ``` -------------------------------- ### Docker Hub Push Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Logs into Docker Hub and tags/pushes the Docker image with specific versions. ```bash docker login ``` ```bash docker tag openhabittracker jinjinov/openhabittracker:1.2.1 docker push jinjinov/openhabittracker:1.2.1 ``` ```bash docker tag openhabittracker jinjinov/openhabittracker:latest docker push jinjinov/openhabittracker:latest ``` -------------------------------- ### Using Expect().ToBeVisibleAsync for Reliable Visibility Checks Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.EndToEndTests/TODO.md Similar to counting, Page.Locator.IsVisibleAsync() provides a snapshot. Use Expect().ToBeVisibleAsync() to wait for an element to become visible, ensuring tests are not flaky due to timing issues. ```csharp await Expect(Page.Locator("...")).ToBeVisibleAsync(); ``` -------------------------------- ### IIS Deployment Command Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.md Command to publish the Blazor WASM project for IIS deployment. Ensure you are in the project directory or provide the correct path to the .csproj file. The output is directed to C:/inetpub/wwwroot. ```bash dotnet publish e:/Jinjinov/OpenHabitTracker/OpenHabitTracker.Blazor.Wasm/OpenHabitTracker.Blazor.Wasm.csproj -c Release -o C:/inetpub/wwwroot ``` -------------------------------- ### Export Data in Various Formats Source: https://context7.com/jinjinov/openhabittracker/llms.txt Handles data export in JSON, YAML, TSV, and Markdown formats. Use this service to back up your data in a preferred format. ```csharp public class ImportExportService { // Export data in specified format public async Task GetDataExportFileString(FileFormat fileFormat) { return fileFormat switch { FileFormat.Json => await _jsonImportExport.GetDataExportFileString(), FileFormat.Tsv => await _tsvImportExport.GetDataExportFileString(), FileFormat.Yaml => await _yamlImportExport.GetDataExportFileString(), FileFormat.Md => await _markdownImportExport.GetDataExportFileString(), _ => throw new ArgumentOutOfRangeException(nameof(fileFormat)), }; } // Import data from file (format detected by extension) public async Task ImportDataFile(string filename, Stream stream); } ``` ```csharp // Example: Export all data as JSON string jsonData = await importExportService.GetDataExportFileString(FileFormat.Json); await File.WriteAllTextAsync("backup.json", jsonData); ``` ```csharp // Example: Export as YAML string yamlData = await importExportService.GetDataExportFileString(FileFormat.Yaml); ``` ```csharp // Example: Export as TSV (spreadsheet-compatible) string tsvData = await importExportService.GetDataExportFileString(FileFormat.Tsv); ``` ```csharp // Example: Import from JSON file using FileStream stream = File.OpenRead("backup.json"); await importExportService.ImportDataFile("backup.json", stream); ``` ```csharp // Example: Import from Google Keep ZIP (Takeout) using FileStream stream = File.OpenRead("takeout.zip"); await importExportService.ImportDataFile("takeout.zip", stream); ``` -------------------------------- ### Verify Git Tags Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Commands to check local and remote git tags for the project. ```bash git rev-parse 1.2.1 ``` ```bash git ls-remote https://github.com/Jinjinov/OpenHabitTracker.git refs/tags/1.2.1 ``` -------------------------------- ### Lazy Loading Pattern in ClientState Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.md Demonstrates the lazy loading pattern for Habits, which also triggers loading of related data like Categories and Times. This pattern is used to manage potentially large datasets efficiently. ```csharp if (Habits is null) { await LoadCategories(); await LoadTimes(); Habits = (await DataAccess.GetHabits()) .Select(x => x.ToModel()) .ToDictionary(x => x.Id); // wire TimesDone from Times dict } ``` -------------------------------- ### Legacy Docker Compose Commands Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Legacy commands for managing Docker Compose services using the older docker-compose tool. ```bash docker-compose build ``` ```bash docker-compose up -d ``` -------------------------------- ### Configure Authentication Services Source: https://github.com/jinjinov/openhabittracker/blob/main/CloudStorage.md C# extension method to register cookie and external OAuth authentication providers, including token storage and claim mapping. ```csharp using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; using System.Security.Claims; using System.Text.Json; namespace OpenHabitTracker.Blazor.Web; public static class AuthenticationSetup { public static IServiceCollection AddAuthenticationProviders(this IServiceCollection services) { services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; // Default for external providers }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.LoginPath = "/login"; options.LogoutPath = "/logout"; options.ExpireTimeSpan = TimeSpan.FromDays(14); // Remember login for 14 days }) .AddGoogle(GoogleDefaults.AuthenticationScheme, options => { options.ClientId = "Your-Google-Client-Id"; options.ClientSecret = "Your-Google-Client-Secret"; options.Scope.Add("email"); options.Scope.Add("profile"); options.SaveTokens = true; options.Events.OnCreatingTicket = async context => { var identity = context.Principal.Identity as ClaimsIdentity; var email = context.Principal.FindFirst(ClaimTypes.Email)?.Value; var name = context.Principal.FindFirst(ClaimTypes.Name)?.Value; identity.AddClaim(new Claim("email", email ?? string.Empty)); identity.AddClaim(new Claim("name", name ?? string.Empty)); // Save tokens for later API calls if needed var tokens = JsonSerializer.Serialize(context.Properties.GetTokens()); identity.AddClaim(new Claim("tokens", tokens)); }; }) .AddMicrosoftAccount(options => { options.ClientId = "Your-OneDrive-Client-Id"; options.ClientSecret = "Your-OneDrive-Client-Secret"; options.SaveTokens = true; options.Scope.Add("email"); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Events.OnCreatingTicket = async context => { ``` -------------------------------- ### Publish OpenHabitTracker MAUI for iOS Source: https://github.com/jinjinov/openhabittracker/blob/main/Release.md Publishes the iOS application with specific signing keys and provisioning profiles. ```bash dotnet publish OpenHabitTracker.Blazor.Maui.csproj -c:Release -f:net9.0-ios -p:ArchiveOnBuild=true -p:RuntimeIdentifier=ios-arm64 -p:CodesignKey="Apple Distribution: Urban Dzindzinovic (53V66WG4KU)" -p:CodesignProvision="openhabittracker.ios" ``` -------------------------------- ### Update EF Core Tools Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Update the global EF Core tools to the latest version before adding migrations. ```bash dotnet tool update --global dotnet-ef ``` -------------------------------- ### Define Content Models in C# Source: https://context7.com/jinjinov/openhabittracker/llms.txt Base classes for notes, tasks, and habits that share common properties like title, priority, and timestamps. ```csharp // ContentModel is the base class for all content public class ContentModel { internal long Id { get; set; } internal long CategoryId { get; set; } public string Title { get; set; } = string.Empty; public string Color { get; set; } = "bg-body-secondary"; public Priority Priority { get; set; } public bool IsDeleted { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } } // ItemsModel extends ContentModel with checklist items and duration public class ItemsModel : ContentModel { public List? Items { get; set; } public TimeOnly? Duration { get; set; } } // NoteModel adds markdown content public class NoteModel : ContentModel { public string Content { get; set; } = string.Empty; internal string ContentMarkdown { get; set; } = string.Empty; } // TaskModel adds planning and completion tracking public class TaskModel : ItemsModel { public DateTime? PlannedAt { get; set; } public DateTime? StartedAt { get; set; } public DateTime? CompletedAt { get; set; } internal TimeSpan? TimeSpent => CompletedAt - StartedAt; } // HabitModel adds recurrence and time tracking public class HabitModel : ItemsModel { public int RepeatCount { get; set; } = 1; public int RepeatInterval { get; set; } = 1; public Period RepeatPeriod { get; set; } = Period.Day; public DateTime? LastTimeDoneAt { get; set; } public List? TimesDone { get; set; } } ``` -------------------------------- ### GitHub Container Registry Push Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Logs into GitHub Container Registry using a personal access token and tags/pushes the Docker image. ```bash echo | docker login ghcr.io -u Jinjinov --password-stdin ``` ```bash docker tag openhabittracker ghcr.io/jinjinov/openhabittracker:1.2.1 docker push ghcr.io/jinjinov/openhabittracker:1.2.1 ``` ```bash docker tag openhabittracker ghcr.io/jinjinov/openhabittracker:latest docker push ghcr.io/jinjinov/openhabittracker:latest ``` -------------------------------- ### Registering Core Services in Blazor WASM Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.md This code snippet shows how to register core services, data access, backup, and Blazor-specific services in a Blazor WASM application's Program.cs file. It also includes platform-specific scoped service registrations. ```csharp builder.Services.AddServices(); // core services (Startup.cs) builder.Services.AddDataAccess(); // IndexedDB or EF Core builder.Services.AddBackup(); // import/export builder.Services.AddBlazor(); // Blazor-specific (JS interop, etc.) // platform-specific: builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); ``` -------------------------------- ### Observe Element Width Interface (C#) Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md Defines the C# interface signature for observing element width changes. This method takes an ID, the element reference, and a .NET object reference to a Blazor component. ```csharp ValueTask ObserveElementWidth(string id, ElementReference element, DotNetObjectReference dotnetRef); ``` -------------------------------- ### Implement IDataAccess Interface Source: https://context7.com/jinjinov/openhabittracker/llms.txt Contract for CRUD operations on entities, implemented by both browser-based IndexedDB and server-side SQLite providers. ```csharp // IDataAccess provides CRUD operations for all entity types public interface IDataAccess { bool MultipleServicesCanModifyData { get; } DataLocation DataLocation { get; } Task Initialize(); // Single entity operations Task AddHabit(HabitEntity habit); Task AddNote(NoteEntity note); Task AddTask(TaskEntity task); Task AddTime(TimeEntity time); Task AddItem(ItemEntity item); Task AddCategory(CategoryEntity category); // Bulk operations Task AddHabits(IReadOnlyList habits); Task AddNotes(IReadOnlyList notes); Task AddTasks(IReadOnlyList tasks); // Query operations with optional filtering Task> GetHabits(); Task> GetNotes(); Task> GetTasks(); Task> GetTimes(long? habitId = null); Task> GetItems(long? parentId = null); // Single entity retrieval Task GetHabit(long id); Task GetNote(long id); Task GetTask(long id); // Update and remove operations Task UpdateHabit(HabitEntity habit); Task UpdateNote(NoteEntity note); Task UpdateTask(TaskEntity task); Task RemoveHabit(long id); Task RemoveNote(long id); Task RemoveTask(long id); Task DeleteAllUserData(); } ``` -------------------------------- ### Docker Compose Configuration for Self-Hosting Source: https://context7.com/jinjinov/openhabittracker/llms.txt Defines the Docker service for OpenHabitTracker, including port mapping, environment variables for authentication, and volume mounting for persistent data. ```yaml # docker-compose.yml services: openhabittracker: image: jinjinov/openhabittracker:latest ports: - "5000:8080" environment: - AppSettings__UserName=${APPSETTINGS_USERNAME} - AppSettings__Email=${APPSETTINGS_EMAIL} - AppSettings__Password=${APPSETTINGS_PASSWORD} - AppSettings__JwtSecret=${APPSETTINGS_JWT_SECRET} volumes: - ./.OpenHabitTracker:/app/.OpenHabitTracker ``` -------------------------------- ### Force Write WAL to DB Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Forces a write from the WAL file to the main database file using a PRAGMA command. This is useful for ensuring data is persisted. ```csharp SaveChanges(); // force write from .db-wal to .db with: context.Database.ExecuteSqlRaw("PRAGMA wal_checkpoint(TRUNCATE);"); ``` -------------------------------- ### Observe Element Width Changes (JavaScript) Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md This function observes the width of a given DOM element and invokes a .NET method asynchronously when the width changes. It uses a `ResizeObserver` and stores the observer and .NET reference for later disconnection. Ensure the .NET method `OnWidthChanged` is defined to handle the width updates. ```javascript export function observeElementWidth(id, element, dotnetRef) { const ro = new ResizeObserver(entries => { for (const entry of entries) { const w = Math.round(entry.contentRect.width); if (w !== lastWidth) { // 1. width-change guard lastWidth = w; dotnetRef.invokeMethodAsync('OnWidthChanged', w) // 2. swallow navigation race .catch(() => {}); // must be .catch, not try-catch (Promise) } } }); ro.observe(element); _resizeObservers.set(id, { ro, dotnetRef }); } ``` -------------------------------- ### Configure QueryParameters for Filtering and Sorting Source: https://context7.com/jinjinov/openhabittracker/llms.txt Defines parameters for filtering and sorting content. Use to specify search terms, date filters, category filters, and sorting criteria. ```csharp // QueryParameters configures filtering and sorting public class QueryParameters { public string? SearchTerm { get; set; } public bool MatchCase { get; set; } public DateTime? DoneAtFilter { get; set; } public DateCompare DoneAtCompare { get; set; } = DateCompare.On; public DateTime? PlannedAtFilter { get; set; } public DateCompare PlannedAtCompare { get; set; } = DateCompare.On; public bool HideCompletedTasks { get; set; } public bool ShowOnlyOverSelectedRatioMin { get; set; } public int SelectedRatioMin { get; set; } = 50; public Ratio SelectedRatio { get; set; } public FilterDisplay CategoryFilterDisplay { get; set; } public long? SelectedCategoryId { get; set; } public List HiddenCategoryIds { get; set; } = []; public Dictionary ShowPriority { get; set; } public Dictionary SortBy { get; set; } } ``` ```csharp // Example: Filter notes by search term and priority QueryParameters query = new() { SearchTerm = "meeting", MatchCase = false, SortBy = new() { { ContentType.Note, Sort.Priority } } }; IEnumerable filteredNotes = notes.FilterNotes(query); ``` ```csharp // Example: Filter tasks by planned date QueryParameters query = new() { PlannedAtFilter = DateTime.Today, PlannedAtCompare = DateCompare.On, HideCompletedTasks = true, SortBy = new() { { ContentType.Task, Sort.PlannedAt } } }; IEnumerable todaysTasks = tasks.FilterTasks(query); ``` ```csharp // Example: Filter habits by urgency ratio QueryParameters query = new() { ShowOnlyOverSelectedRatioMin = true, SelectedRatioMin = 100, // Show overdue habits SelectedRatio = Ratio.ElapsedToDesired, SortBy = new() { { ContentType.Habit, Sort.SelectedRatio } } }; IEnumerable urgentHabits = habits.FilterHabits(query); ``` ```csharp // Example: Filter by category QueryParameters query = new() { CategoryFilterDisplay = FilterDisplay.Dropdown, SelectedCategoryId = workCategoryId }; ``` -------------------------------- ### Configure Docker Environment Variables Source: https://github.com/jinjinov/openhabittracker/blob/main/README.md Set environment variables for the OpenHabitTracker Docker image to configure username, email, password, and JWT secret. ```env APPSETTINGS_USERNAME=admin APPSETTINGS_EMAIL=admin@admin.com APPSETTINGS_PASSWORD=admin APPSETTINGS_JWT_SECRET=your-extremely-strong-secret-key ``` -------------------------------- ### Implement Status Service for Accessibility Feedback Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md This C# code defines a Scoped service for managing status messages, intended for accessibility feedback. It includes methods to set and clear messages and an event to notify listeners of changes. Register this service in your dependency injection container. ```csharp public class StatusService { public string Message { get; private set; } public event Action? OnChange; public void Set(string msg) { Message = msg; OnChange?.Invoke(); } public void Clear() { Message = string.Empty; OnChange?.Invoke(); } } ``` ```csharp builder.Services.AddScoped(); ``` -------------------------------- ### Observe Element Width in Blazor Component (C#) Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md This code block within the `OnAfterRenderAsync` method of a Blazor component initializes the JavaScript observer on the first render. It creates a `DotNetObjectReference` and calls the JavaScript `ObserveElementWidth` function, passing the observer ID, the element reference, and the .NET object reference. ```csharp if (firstRender) { _dotNetRef = DotNetObjectReference.Create(this); await JsInterop.ObserveElementWidth(_observerId, columnRef, _dotNetRef); } ``` -------------------------------- ### Blazor Lifecycle Method Error Handling Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Exceptions in Blazor lifecycle methods like `OnInitializedAsync` are fatal. Ensure these methods handle exceptions gracefully or remove them if not needed. ```csharp protected override async Task OnInitializedAsync() ``` -------------------------------- ### Using Expect().ToHaveCountAsync for Reliable Counting Source: https://github.com/jinjinov/openhabittracker/blob/main/OpenHabitTracker.EndToEndTests/TODO.md Instead of Page.Locator.CountAsync(), which returns an immediate snapshot, use Expect().ToHaveCountAsync(n) to ensure the test waits for the element count to meet the specified condition, preventing flakiness. ```csharp await Expect(Page.Locator("...")).ToHaveCountAsync(n); ``` -------------------------------- ### NoteService Operations Source: https://context7.com/jinjinov/openhabittracker/llms.txt Handles note management with Markdown support and editing capabilities. ```csharp public class NoteService : INoteService { public IReadOnlyCollection? Notes { get; } public NoteModel? SelectedNote { get; set; } public NoteModel? NewNote { get; set; } public async Task Initialize() { await _clientState.LoadCategories(); await _clientState.LoadNotes(); } public IEnumerable GetNotes() { QueryParameters queryParameters = _searchFilterService.GetQueryParameters(_clientState.Settings); return Notes!.FilterNotes(queryParameters); } } // Example: Creating a new note with Markdown content NoteModel newNote = new() { Title = "Meeting Notes", Content = @"# Meeting Summary ## Action Items - [ ] Review proposal - [ ] Send follow-up email > Important: Deadline is Friday", Priority = Priority.Medium, CategoryId = categoryId }; noteService.NewNote = newNote; await noteService.AddNote(); // Example: Select a note for editing noteService.SetSelectedNote(noteId); // Example: Update the selected note noteService.SelectedNote.Title = "Updated Meeting Notes"; noteService.SelectedNote.Content = "Updated content..."; await noteService.UpdateNote(); // Example: Soft-delete a note await noteService.DeleteNote(note); ``` -------------------------------- ### Docker Compose Commands Source: https://github.com/jinjinov/openhabittracker/blob/main/DeveloperNotes.md Common commands for managing Docker Compose services. Note that docker-compose.yml is not used for UI-based Docker image runs. ```bash docker compose up ``` ```bash docker compose down ``` ```bash docker compose build ``` ```bash docker compose ps ``` ```bash docker compose logs ``` ```bash docker compose stop ``` ```bash docker compose start ``` ```bash docker compose restart ``` ```bash docker compose exec ``` ```bash docker compose run ``` -------------------------------- ### Environment Variables for Docker Source: https://context7.com/jinjinov/openhabittracker/llms.txt Specifies the environment variables required for the Docker container, including username, email, password, and JWT secret for authentication. ```bash # .env file for credentials APPSETTINGS_USERNAME=admin APPSETTINGS_EMAIL=admin@admin.com APPSETTINGS_PASSWORD=your-secure-password APPSETTINGS_JWT_SECRET=your-extremely-strong-secret-key ``` -------------------------------- ### Handle Width Changes in Blazor Component (C#) Source: https://github.com/jinjinov/openhabittracker/blob/main/TODO.md This C# method, marked with `[JSInvokable]`, is called from JavaScript when the observed element's width changes. It updates the component's state and ensures the UI is re-rendered using `InvokeAsync(StateHasChanged)`. Returning a `Task` is important for proper Promise handling in JavaScript. ```csharp [JSInvokable] public async Task OnWidthChanged(int width) { columnWidth = width; await InvokeAsync(StateHasChanged); } ```