### Get Subscription (Response) Source: https://context7.com/amantinband/clean-architecture/llms.txt Shows an example of the response when retrieving a subscription, with JSON details about the subscription. ```bash { "id": "c8ee11f0-d4bb-4b43-a448-d511924b520e", "userId": "aae93bf5-9e3c-47b3-aace-3034653b6bb2", "subscriptionType": "Basic" } ``` -------------------------------- ### Install Clean Architecture Template Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Installs the Amantinband.CleanArchitecture.Template using the .NET CLI. This command is typically run once to make the template available for creating new projects. ```shell dotnet new install Amantinband.CleanArchitecture.Template ``` -------------------------------- ### Create New Clean Architecture Project Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Creates a new Clean Architecture project using the installed template. The `-o` flag specifies the output directory for the new project. ```shell dotnet new clean-arch -o CleanArchitecture ``` -------------------------------- ### Run the Application (Bash) Source: https://context7.com/amantinband/clean-architecture/llms.txt Instructions for running the Clean Architecture application. This can be done using Docker Compose for easy setup or via the .NET CLI. The application will automatically apply database migrations on startup and is accessible at `http://localhost:5001`, with Swagger UI available at `http://localhost:5001/swagger`. An SQLite database will be created. ```bash # Using Docker Compose docker compose up # Using .NET CLI dotnet run --project src/CleanArchitecture.Api # Application starts on http://localhost:5001 # Swagger UI available at http://localhost:5001/swagger # Database migrations applied automatically on startup # SQLite database created at src/CleanArchitecture.Api/CleanArchitecture.sqlite ``` -------------------------------- ### Get Subscription (HTTP) Source: https://context7.com/amantinband/clean-architecture/llms.txt This endpoint retrieves subscription details for a specific user. It uses a GET request to the subscriptions endpoint. A 200 OK response is expected, containing JSON data representing the subscription details. ```http GET http://localhost:5001/users/aae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` -------------------------------- ### Run Service with Docker Compose Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Starts the Clean Architecture service using Docker Compose. This command assumes a `docker-compose.yml` file is present in the project root, orchestrating the service's containers. ```shell docker compose up ``` -------------------------------- ### Implement CQRS Command Handler (C#) Source: https://context7.com/amantinband/clean-architecture/llms.txt An example of a CQRS command handler in C#, demonstrating authorization, validation, and error handling using MediatR and related libraries. The handler uses dependency injection and interacts with repositories to persist changes. ```csharp using CleanArchitecture.Application.Common.Interfaces; using CleanArchitecture.Application.Common.Security.Permissions; using CleanArchitecture.Application.Common.Security.Policies; using CleanArchitecture.Application.Common.Security.Request; using CleanArchitecture.Domain.Reminders; using ErrorOr; using MediatR; namespace CleanArchitecture.Application.Reminders.Commands.SetReminder; // Command with declarative authorization [Authorize(Permissions = Permission.Reminder.Set, Policies = Policy.SelfOrAdmin)] public record SetReminderCommand( Guid UserId, Guid SubscriptionId, string Text, DateTime DateTime) : IAuthorizeableRequest>; // Command handler with dependency injection public class SetReminderCommandHandler( IUsersRepository usersRepository, IRemindersRepository remindersRepository) : IRequestHandler> { public async Task> Handle( SetReminderCommand command, CancellationToken cancellationToken) { // Create reminder entity var reminder = new Reminder( Guid.NewGuid(), command.UserId, command.SubscriptionId, command.Text, command.DateTime); // Get user by subscription ID var user = await usersRepository.GetBySubscriptionIdAsync( command.SubscriptionId, cancellationToken); if (user is null) { return Error.NotFound(description: "User not found"); } // Domain logic validates subscription limits var setReminderResult = user.SetReminder(reminder); if (setReminderResult.IsError) { return setReminderResult.Errors; } // Update aggregate root - domain events queued automatically await usersRepository.UpdateAsync(user, cancellationToken); return reminder; } } ``` -------------------------------- ### Role-Based Authorization in C# Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Demonstrates role-based authorization in C# using the `Authorize` attribute. This example restricts access to the `CancelSubscriptionCommand` to users with the 'Admin' role by implementing the `IAuthorizeableRequest` interface. ```csharp [Authorize(Roles = "Admin")] public record CancelSubscriptionCommand(Guid UserId, Guid SubscriptionId) : IAuthorizeableRequest>; ``` -------------------------------- ### GET /users/{userId}/subscriptions/{subscriptionId}/reminders - List All Reminders Source: https://context7.com/amantinband/clean-architecture/llms.txt Retrieves all reminders associated with a specific subscription. Authorization checks are performed. ```APIDOC ## GET /users/{userId}/subscriptions/{subscriptionId}/reminders ### Description Retrieve all reminders for a specific subscription with authorization checks. ### Method GET ### Endpoint http://localhost:5001/users/{userId}/subscriptions/{subscriptionId}/reminders ### Parameters #### Path Parameters - **userId** (string) - Required - The ID of the user. - **subscriptionId** (string) - Required - The ID of the user's subscription. ### Response #### Success Response (200 OK) - Returns an array of reminder objects. - **id** (string) - The ID of the reminder. - **text** (string) - The content of the reminder. - **dateTime** (string) - The scheduled date and time for the reminder. - **isDismissed** (boolean) - Indicates if the reminder has been dismissed. #### Response Example ```json [ { "id": "08233bb1-ce29-49e2-b346-5f8b7cf61593", "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09", "isDismissed": false }, { "id": "a1b2c3d4-e5f6-4321-9876-543210fedcba", "text": "Call dentist for appointment", "dateTime": "2024-02-17T09:00:00", "isDismissed": true } ] ``` ``` -------------------------------- ### Get Subscription API Source: https://context7.com/amantinband/clean-architecture/llms.txt Retrieves the details of a specific user's subscription. ```APIDOC ## GET /users/{userId}/subscriptions ### Description Retrieve subscription details for a specific user. ### Method GET ### Endpoint /users/{userId}/subscriptions ### Parameters #### Path Parameters - **userId** (string) - Required - The unique identifier for the user whose subscription details are to be retrieved. ### Request Example ```http GET http://localhost:5001/users/aae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ### Response #### Success Response (200 OK) - **id** (string) - The unique identifier for the subscription. - **userId** (string) - The unique identifier for the user associated with the subscription. - **subscriptionType** (string) - The type of the subscription (e.g., "Basic"). #### Response Example (200 OK) ```json { "id": "c8ee11f0-d4bb-4b43-a448-d511924b520e", "userId": "aae93bf5-9e3c-47b3-aace-3034653b6bb2", "subscriptionType": "Basic" } ``` ``` -------------------------------- ### GET /users/{userId}/subscriptions/{subscriptionId}/reminders/{reminderId} - Get Single Reminder Source: https://context7.com/amantinband/clean-architecture/llms.txt Retrieves the details of a specific reminder identified by its ID. ```APIDOC ## GET /users/{userId}/subscriptions/{subscriptionId}/reminders/{reminderId} ### Description Retrieve a specific reminder by ID with full details. ### Method GET ### Endpoint http://localhost:5001/users/{userId}/subscriptions/{subscriptionId}/reminders/{reminderId} ### Parameters #### Path Parameters - **userId** (string) - Required - The ID of the user. - **subscriptionId** (string) - Required - The ID of the user's subscription. - **reminderId** (string) - Required - The ID of the reminder to retrieve. ### Response #### Success Response (200 OK) - **id** (string) - The ID of the reminder. - **text** (string) - The content of the reminder. - **dateTime** (string) - The scheduled date and time for the reminder. - **isDismissed** (boolean) - Indicates if the reminder has been dismissed. #### Response Example ```json { "id": "08233bb1-ce29-49e2-b346-5f8b7cf61593", "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09", "isDismissed": false } ``` ``` -------------------------------- ### List All Reminders for Subscription in .NET API Source: https://context7.com/amantinband/clean-architecture/llms.txt Retrieves all reminders associated with a specific user subscription via GET request with Bearer authorization. Includes details like ID, text, dateTime, and dismissal status. Response is a 200 OK JSON array; enforces permission checks and subscription ownership. ```http GET http://localhost:5001/users/aae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions/c8ee11f0-d4bb-4b43-a448-d511924b520e/reminders Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ```json [ { "id": "08233bb1-ce29-49e2-b346-5f8b7cf61593", "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09", "isDismissed": false }, { "id": "a1b2c3d4-e5f6-4321-9876-543210fedcba", "text": "Call dentist for appointment", "dateTime": "2024-02-17T09:00:00", "isDismissed": true } ] ``` -------------------------------- ### Get Single Reminder by ID in .NET API Source: https://context7.com/amantinband/clean-architecture/llms.txt Fetches details of a specific reminder by ID within a user subscription path using GET with Bearer auth. Returns 200 OK JSON with reminder ID, text, dateTime, and dismissal status. Limited to authorized users; depends on infrastructure for data retrieval. ```http GET http://localhost:5001/users/aae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions/c8ee11f0-d4bb-4b43-a448-d511924b520e/reminders/08233bb1-ce29-49e2-b346-5f8b7cf61593 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ```json { "id": "08233bb1-ce29-49e2-b346-5f8b7cf61593", "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09", "isDismissed": false } ``` -------------------------------- ### Dismiss Reminder (Error Response) Source: https://context7.com/amantinband/clean-architecture/llms.txt Shows an example of Conflict error response when attempting to dismiss an already dismissed reminder. The response is a JSON object containing error details and an HTTP status code. ```bash { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.9", "title": "Conflict", "status": 409, "detail": "Reminder already dismissed" } ``` -------------------------------- ### Run Tests (Bash) Source: https://context7.com/amantinband/clean-architecture/llms.txt Commands to execute the test suite for the Clean Architecture project. Supports running all tests, specific test projects (Domain, Application, API), and tests with code coverage collection. It also outlines different categories of tests available. ```bash # Run all tests dotnet test # Run specific test project dotnet test tests/CleanArchitecture.Domain.UnitTests dotnet test tests/CleanArchitecture.Application.UnitTests dotnet test tests/CleanArchitecture.Application.SubcutaneousTests dotnet test tests/CleanArchitecture.Api.IntegrationTests # Run with coverage dotnet test --collect:"XPlat Code Coverage" # Test categories: # - Domain Unit Tests: Entity invariants and business logic # - Application Unit Tests: Pipeline behaviors (validation, authorization) # - Subcutaneous Tests: End-to-end use case testing without HTTP ``` -------------------------------- ### POST /users/{userId}/subscriptions - Create Subscription Source: https://context7.com/amantinband/clean-architecture/llms.txt Creates a new user subscription. Requires authentication and specifies the subscription type (Basic or Pro). ```APIDOC ## POST /users/{userId}/subscriptions ### Description Create a new user subscription with specified type (Basic or Pro). ### Method POST ### Endpoint http://localhost:5001/users/{userId}/subscriptions ### Parameters #### Path Parameters - **userId** (string) - Required - The ID of the user for whom to create the subscription. #### Request Body - **FirstName** (string) - Required - User's first name. - **LastName** (string) - Required - User's last name. - **Email** (string) - Required - User's email address. - **SubscriptionType** (string) - Required - The type of subscription ('Basic' or 'Pro'). ### Request Example ```json { "FirstName": "Lior", "LastName": "Dagan", "Email": "lior@dagan.com", "SubscriptionType": "Basic" } ``` ### Response #### Success Response (201 Created) - **id** (string) - The ID of the newly created subscription. - **userId** (string) - The ID of the user associated with the subscription. - **subscriptionType** (string) - The type of the subscription. #### Response Example ```json { "id": "c8ee11f0-d4bb-4b43-a448-d511924b520e", "userId": "aae93bf5-9e3c-47b3-aace-3034653b6bb2", "subscriptionType": "Basic" } ``` ``` -------------------------------- ### Create Subscription Request (HTTP) Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Defines an HTTP POST request to create a subscription for a user. It includes the user ID in the URL and sends subscription details in the request body, authenticated with a bearer token. ```http POST {{host}}/users/{{userId}}/subscriptions Content-Type: application/json Authorization: Bearer {{token}} { "SubscriptionType": "Basic" } ``` -------------------------------- ### Configure REST Client Environment Variables (VS Code) Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Configures environment variables for the REST Client extension in VS Code. This allows for dynamic setting of variables like `host`, `token`, and `userId` across HTTP files, simplifying API testing. ```json { "rest-client.environmentVariables": { "$shared": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiTGlvciIsImZhbWlseV9uYW1lIjoiRGFnYW4iLCJlbWFpbCI6Imxpb3JAZGFnYW4uY29tIiwiaWQiOiJhYWU5M2JmNS05ZTNjLTQ3YjMtYWFjZS0zMDM0NjUzYjZiYjIiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsInBlcm1pc3Npb25zIjpbInNldDpyZW1pbmRlciIsImdldDpyZW1pbmRlciIsImRpc21pc3M6cmVtaW5kZXIiLCJkZWxldGU6cmVtaW5kZXIiLCJjcmVhdGU6c3Vic2NyaXB0aW9uIiwiZGVsZXRlOnN1YnNjcmlwdGlvbiIsImdldDpzdWJzY3JpcHRpb24iXSwiZXhwIjoxNzA0MTM0MTIzLCJpc3MiOiJSZW1pbmRlclNlcnZpY2UiLCJhdWQiOiJSZW1pbmRlclNlcnZpY2UifQ.wyvn9cq3ohp-JPTmbBd3G1cAU1A6COpiQd3C_e_Ng5s", "userId": "aae93bf5-9e3c-47b3-aace-3034653b6bb2", "subscriptionId": "c8ee11f0-d4bb-4b43-a448-d511924b520e", "reminderId": "08233bb1-ce29-49e2-b346-5f8b7cf61593" }, "dev": { "host": "http://localhost:5001" }, "prod": { "host": "http://your-prod-endpoint.com" } } } ``` -------------------------------- ### Create Reminder Request (HTTP) Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Defines an HTTP POST request to create a reminder for a user's subscription. It specifies the user ID and subscription ID in the URL and includes reminder details like text and date in the body, secured by an authorization token. ```http POST {{host}}/users/{{userId}}/subscriptions/{{subscriptionId}}/reminders Content-Type: application/json Authorization: Bearer {{token}} { "text": "let's do it", "dateTime": "2025-2-26" } ``` -------------------------------- ### Run Service with .NET CLI Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Runs the Clean Architecture API service using the .NET CLI. This command targets the specific project file for the API, typically used for local development and testing. ```shell dotnet run --project src/CleanArchitecture.Api ``` -------------------------------- ### POST /users/{userId}/subscriptions/{subscriptionId}/reminders - Create Reminder Source: https://context7.com/amantinband/clean-architecture/llms.txt Sets a new reminder for a specific date and time. This endpoint includes validation against subscription limits. ```APIDOC ## POST /users/{userId}/subscriptions/{subscriptionId}/reminders ### Description Set a new reminder for a specific date and time with validation against subscription limits. ### Method POST ### Endpoint http://localhost:5001/users/{userId}/subscriptions/{subscriptionId}/reminders ### Parameters #### Path Parameters - **userId** (string) - Required - The ID of the user. - **subscriptionId** (string) - Required - The ID of the user's subscription. #### Request Body - **text** (string) - Required - The content of the reminder. - **dateTime** (string) - Required - The date and time for the reminder in ISO 8601 format (e.g., 'YYYY-MM-DDTHH:mm:ss'). ### Request Example ```json { "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09" } ``` ### Response #### Success Response (201 Created) - **id** (string) - The ID of the newly created reminder. - **text** (string) - The content of the reminder. - **dateTime** (string) - The scheduled date and time for the reminder. - **isDismissed** (boolean) - Indicates if the reminder has been dismissed. #### Response Example ```json { "id": "08233bb1-ce29-49e2-b346-5f8b7cf61593", "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09", "isDismissed": false } ``` #### Error Response (400 Bad Request) - **type** (string) - Error type URI. - **title** (string) - Error title. - **status** (integer) - HTTP status code. - **detail** (string) - Detailed error message, e.g., "Cannot create more reminders than subscription allows". #### Error Example ```json { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "Bad Request", "status": 400, "detail": "Cannot create more reminders than subscription allows" } ``` ``` -------------------------------- ### Configure Background Service for Email Notifications (C#) Source: https://context7.com/amantinband/clean-architecture/llms.txt Implements a scheduled background service using C# to send reminder emails periodically. This service relies on `FluentEmail.Core` for email sending and `Microsoft.Extensions.Hosting` for background task management. It fetches reminders and user data from repositories and sends notifications to users. Configuration is managed via `appsettings.json`. ```csharp using CleanArchitecture.Application.Common.Interfaces; using FluentEmail.Core; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; namespace CleanArchitecture.Infrastructure.Reminders.BackgroundServices; public class ReminderEmailBackgroundService( IServiceProvider serviceProvider, IOptions emailSettings) : IHostedService { private Timer? _timer; public Task StartAsync(CancellationToken cancellationToken) { // Run every minute _timer = new Timer( SendEmailNotifications, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); return Task.CompletedTask; } private async void SendEmailNotifications(object? state) { if (!emailSettings.Value.EnableEmailNotifications) { return; } using var scope = serviceProvider.CreateScope(); var remindersRepository = scope.ServiceProvider .GetRequiredService(); var usersRepository = scope.ServiceProvider .GetRequiredService(); var fluentEmail = scope.ServiceProvider .GetRequiredService(); var now = DateTime.UtcNow; var remindersDue = await remindersRepository .ListAsync(r => r.DateTime >= now && r.DateTime < now.AddMinutes(1) && !r.IsDismissed); // Group reminders by user var remindersByUser = remindersDue .GroupBy(r => r.UserId) .ToDictionary(g => g.Key, g => g.ToList()); foreach (var (userId, reminders) in remindersByUser) { var user = await usersRepository.GetByIdAsync(userId); await fluentEmail .To(user.Email) .Subject($"{reminders.Count} reminders due!") .Body($""" Dear {user.FirstName} {user.LastName} from the present. I hope this email finds you well. I'm writing you this email to remind you about the following reminders: {string.Join('\n', reminders.Select((r, i) => $"{i + 1}. {r.Text}"))} Best, {user.FirstName} from the past. """) .SendAsync(); } } public Task StopAsync(CancellationToken cancellationToken) { _timer?.Dispose(); return Task.CompletedTask; } } // Configure in appsettings.json { "EmailSettings": { "EnableEmailNotifications": true, "DefaultFromEmail": "reminders@example.com", "SmtpSettings": { "Server": "smtp.gmail.com", "Port": 587, "Username": "your-email@gmail.com", "Password": "your-password" } } } ``` -------------------------------- ### Reminder Creation API Source: https://github.com/amantinband/clean-architecture/blob/main/README.md This endpoint allows users to create reminders associated with their subscriptions. It requires the user ID and subscription ID in the path, along with an Authorization token. The request body contains the reminder's text and the date and time it should be active. ```APIDOC ## POST /users/{userId}/subscriptions/{subscriptionId}/reminders ### Description Creates a new reminder for a specific user's subscription. ### Method POST ### Endpoint /users/{userId}/subscriptions/{subscriptionId}/reminders ### Parameters #### Path Parameters - **userId** (string) - Required - The ID of the user. - **subscriptionId** (string) - Required - The ID of the subscription to which the reminder is linked. #### Query Parameters None #### Request Body - **text** (string) - Required - The content of the reminder. - **dateTime** (string) - Required - The date and time for the reminder (format: YYYY-MM-DD). ### Request Example ```http POST {{host}}/users/bae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions/c8ee11f0-d4bb-4b43-a448-d511924b520e/reminders Content-Type: application/json Authorization: Bearer {{token}} { "text": "let's do it", "dateTime": "2025-2-26" } ``` ### Response #### Success Response (200) - **reminderId** (string) - The ID of the newly created reminder. #### Response Example ```json { "reminderId": "08233bb1-ce29-49e2-b346-5f8b7cf61593" } ``` ``` -------------------------------- ### Handling Domain Events with Eventual Consistency in C# Source: https://context7.com/amantinband/clean-architecture/llms.txt This code handles domain events asynchronously after HTTP responses for eventual consistency, such as processing user deletion upon subscription cancellation. It uses MediatR for event publishing and middleware to defer event handling. Dependencies include EF Core for transactions, MediatR for publishing, and custom repositories; inputs include domain events and HTTP context, outputs are none directly but trigger side effects; it relies on domain objects to queue events and middleware to process them post-response. ```csharp using CleanArchitecture.Application.Common.Interfaces; using CleanArchitecture.Domain.Users.Events; using MediatR; namespace CleanArchitecture.Application.Subscriptions.Events; // Event handler for subscription cancellation public class SubscriptionCanceledEventHandler( IUsersRepository usersRepository, IRemindersRepository remindersRepository) : INotificationHandler { public async Task Handle( SubscriptionCanceledEvent notification, CancellationToken cancellationToken) { // Delete user - triggers ReminderDeletedEvent for each reminder notification.User.DeleteAllReminders(); // Remove user from database await usersRepository.RemoveAsync(notification.User, cancellationToken); } } // Middleware processes domain events after response sent public class EventualConsistencyMiddleware(RequestDelegate next) { public async Task InvokeAsync( HttpContext context, IPublisher publisher) { var transaction = await context.RequestServices .GetRequiredService() .Database.BeginTransactionAsync(); await next(context); await transaction.CommitAsync(); // Process domain events after response sent to client context.Response.OnCompleted(async () => { if (context.Items.TryGetValue("DomainEvents", out var value) && value is Queue domainEvents) { // Publish all queued domain events while (domainEvents.TryDequeue(out var domainEvent)) { await publisher.Publish(domainEvent); } } }); } } ``` -------------------------------- ### Create Subscription in .NET Reminder API Source: https://context7.com/amantinband/clean-architecture/llms.txt Creates a new user subscription with Basic or Pro type, requiring user ID in the path and Bearer token authorization. The request body includes user details and subscription type; validates against existing subscriptions. Response returns 201 Created with subscription ID, user ID, and type; limited by user authentication and subscription rules. ```http POST http://localhost:5001/users/aae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... { "FirstName": "Lior", "LastName": "Dagan", "Email": "lior@dagan.com", "SubscriptionType": "Basic" } ``` ```json { "id": "c8ee11f0-d4bb-4b43-a448-d511924b520e", "userId": "aae93bf5-9e3c-47b3-aace-3034653b6bb2", "subscriptionType": "Basic" } ``` -------------------------------- ### Mixed Authorization Types in C# Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Illustrates combining permissions, policies, and roles in a single Authorize attribute or using multiple attributes for complex authorization scenarios. ```csharp [Authorize(Permissions = "get:reminder,list:reminder", Policies = "SelfOrAdmin", Roles = "ReminderManager")] public record ListRemindersQuery(Guid UserId, Guid SubscriptionId, Guid ReminderId) : IAuthorizeableRequest>; ``` ```csharp [Authorize(Permissions = "get:reminder")] [Authorize(Permissions = "list:reminder")] [Authorize(Policies = "SelfOrAdmin")] [Authorize(Roles = "ReminderManager")] public record ListRemindersQuery(Guid UserId, Guid SubscriptionId, Guid ReminderId) : IAuthorizeableRequest>; ``` -------------------------------- ### Clone Clean Architecture Project Repository Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Clones the Clean Architecture project from its GitHub repository. This is used to obtain the project's source code for development or review. ```shell git clone https://github.com/amantinband/clean-architecture ``` -------------------------------- ### Policy-Based Authorization in C# Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Shows how to use the Authorize attribute with the Policy parameter to enforce custom policies. Requires implementing the IAuthorizeableRequest interface and a PolicyEnforcer class. ```csharp [Authorize(Policies = "SelfOrAdmin")] public record GetReminderQuery(Guid UserId, Guid SubscriptionId, Guid ReminderId) : IAuthorizeableRequest>; ``` ```csharp public class PolicyEnforcer : IPolicyEnforcer { public ErrorOr Authorize( IAuthorizeableRequest request, CurrentUser currentUser, string policy) { return policy switch { "SelfOrAdmin" => SelfOrAdminPolicy(request, currentUser), _ => Error.Unexpected(description: "Unknown policy name"), }; } private static ErrorOr SelfOrAdminPolicy(IAuthorizeableRequest request, CurrentUser currentUser) => request.UserId == currentUser.Id || currentUser.Roles.Contains(Role.Admin) ? Result.Success : Error.Unauthorized(description: "Requesting user failed policy requirement"); } ``` -------------------------------- ### Implement User Aggregate Root in C# Source: https://context7.com/amantinband/clean-architecture/llms.txt This C# class represents a User aggregate root that encapsulates business rules for managing reminders, enforcing subscription-based limits, and raising domain events. It depends on common domain entities like Entity, Reminder, and Subscription, with inputs being reminder objects and outputs as ErrorOr results indicating operation success or failure. Limitations include hardcoded limits and reliance on a Calendar for event tracking. ```csharp using CleanArchitecture.Domain.Common; using CleanArchitecture.Domain.Reminders; using CleanArchitecture.Domain.Users.Events; using ErrorOr; namespace CleanArchitecture.Domain.Users; public class User : Entity { private readonly Calendar _calendar = null!; private readonly List _reminderIds = []; private readonly List _dismissedReminderIds = []; public Subscription Subscription { get; private set; } = null!; public string Email { get; } = null!; public string FirstName { get; } = null!; public string LastName { get; } = null!; public User( Guid id, string firstName, string lastName, string email, Subscription subscription, Calendar? calendar = null) : base(id) { FirstName = firstName; LastName = lastName; Email = email; Subscription = subscription; _calendar = calendar ?? Calendar.Empty(); } // Business logic with invariant enforcement public ErrorOr SetReminder(Reminder reminder) { // Validate subscription is active if (Subscription == Subscription.Canceled) { return Error.NotFound(description: "Subscription not found"); } // Check daily limit based on subscription type if (HasReachedDailyReminderLimit(reminder.DateTime)) { return UserErrors.CannotCreateMoreRemindersThanSubscriptionAllows; } // Update calendar tracking _calendar.IncrementEventCount(reminder.Date); _reminderIds.Add(reminder.Id); // Raise domain event for eventual consistency _domainEvents.Add(new ReminderSetEvent(reminder)); return Result.Success; } public ErrorOr DeleteReminder(Reminder reminder) { if (Subscription == Subscription.Canceled) { return Error.NotFound(description: "Subscription not found"); } if (!_reminderIds.Remove(reminder.Id)) { return Error.NotFound(description: "Reminder not found"); } // Update calendar and dismissed list _dismissedReminderIds.Remove(reminder.Id); _calendar.DecrementEventCount(reminder.Date); // Raise domain event _domainEvents.Add(new ReminderDeletedEvent(reminder.Id)); return Result.Success; } private bool HasReachedDailyReminderLimit(DateTimeOffset dateTime) { var dailyReminderCount = _calendar.GetNumEventsOnDay(dateTime.Date); return dailyReminderCount >= Subscription.SubscriptionType.GetMaxDailyReminders() || dailyReminderCount == int.MaxValue; } } ``` -------------------------------- ### Generate Token Request (HTTP) Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Defines an HTTP POST request to generate a token. This is a testing endpoint that creates a token based on provided user details. It's intended for development and testing purposes. ```http POST {{host}}/tokens/generate Content-Type: application/json { "Id": "bae93bf5-9e3c-47b3-aace-3034653b6bb2", "FirstName": "Amichai", "LastName": "Mantinband", "Email": "amichai@mantinband.com", "Permissions": [ "set:reminder", "get:reminder", "dismiss:reminder", "delete:reminder", "create:subscription", "delete:subscription", "get:subscription" ], "Roles": [ "Admin" ] } ``` -------------------------------- ### Subscription Management API Source: https://github.com/amantinband/clean-architecture/blob/main/README.md This API allows for the creation of new subscriptions for a user. It requires the user's ID in the path and an Authorization header with a valid token. The request body specifies the type of subscription to be created. ```APIDOC ## POST /users/{userId}/subscriptions ### Description Creates a new subscription for a specified user. ### Method POST ### Endpoint /users/{userId}/subscriptions ### Parameters #### Path Parameters - **userId** (string) - Required - The ID of the user for whom the subscription is being created. #### Query Parameters None #### Request Body - **SubscriptionType** (string) - Required - The type of subscription to create (e.g., "Basic"). ### Request Example ```http POST {{host}}/users/bae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions Content-Type: application/json Authorization: Bearer {{token}} { "SubscriptionType": "Basic" } ``` ### Response #### Success Response (200) - **subscriptionId** (string) - The ID of the newly created subscription. #### Response Example ```json { "subscriptionId": "c8ee11f0-d4bb-4b43-a448-d511924b520e" } ``` ``` -------------------------------- ### Create Reminder with Subscription Validation in .NET API Source: https://context7.com/amantinband/clean-architecture/llms.txt Sets a new time-based reminder linked to a subscription, validating against daily limits (3 for Basic, unlimited for Pro). Requires POST to subscription reminders path with text and dateTime in JSON body, plus Bearer auth. Returns 201 Created on success or 400 Bad Request if limit exceeded; enforces domain business rules. ```http POST http://localhost:5001/users/aae93bf5-9e3c-47b3-aace-3034653b6bb2/subscriptions/c8ee11f0-d4bb-4b43-a448-d511924b520e/reminders Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... { "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09" } ``` ```json { "id": "08233bb1-ce29-49e2-b346-5f8b7cf61593", "text": "Tell my husband I love him", "dateTime": "2024-02-16T22:20:09", "isDismissed": false } ``` ```json { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "Bad Request", "status": 400, "detail": "Cannot create more reminders than subscription allows" } ``` -------------------------------- ### Permission-Based Authorization in C# Source: https://github.com/amantinband/clean-architecture/blob/main/README.md Demonstrates how to use the Authorize attribute with the Permissions parameter to restrict access based on user permissions. Requires implementing the IAuthorizeableRequest interface. ```csharp [Authorize(Permissions = "get:reminder")] public record GetReminderQuery(Guid UserId, Guid SubscriptionId, Guid ReminderId) : IAuthorizeableRequest>; ``` -------------------------------- ### Send Email Notification for Due Reminders Source: https://github.com/amantinband/clean-architecture/blob/main/README.md C# method for a background service to send email notifications for due reminders. It uses FluentEmail to construct and send emails, including a list of reminder texts. The method is asynchronous. ```csharp private async void SendEmailNotifications(object? state) { await _fluentEmail .To(user.Email) .Subject($"{dueReminders.Count} reminders due!") .Body($""" Dear {user.FirstName} {user.LastName} from the present. I hope this email finds you well. I'm writing you this email to remind you about the following reminders: {string.Join('\n', dueReminders.Select((reminder, i) => $"{i + 1}. {reminder.Text}"))} Best, {user.FirstName} from the past. """) .SendAsync(); } ``` -------------------------------- ### Eventual Consistency Middleware Processing Source: https://github.com/amantinband/clean-architecture/blob/main/README.md C# code for an eventual consistency middleware. This middleware processes a queue of domain events after the user receives a response, publishing each event for asynchronous handling by relevant subscribers. ```csharp public async Task InvokeAsync(HttpContext context, IEventualConsistencyProcessor eventualConsistencyProcessor) { context.Response.OnCompleted(async () => { if (context.Items.TryGetValue("DomainEvents", out var value) || value is not Queue domainEvents) { return; } while (domainEvents.TryDequeue(out var nextEvent)) { await publisher.Publish(nextEvent); } }); } ``` -------------------------------- ### Configuring MediatR Pipeline Behaviors in C# Source: https://context7.com/amantinband/clean-architecture/llms.txt This code configures MediatR to include validation and authorization behaviors in the pipeline, running before request handlers. It registers services from assemblies, adds open behaviors for cross-cutting concerns, and validates requests using FluentValidation. Dependencies include MediatR, FluentValidation, and Microsoft DI; inputs are IServiceCollection, outputs are the modified services; it assumes the presence of custom behaviors and validators. ```csharp using CleanArchitecture.Application.Common.Behaviors; using FluentValidation; using MediatR; using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.Application; public static class DependencyInjection { public static IServiceCollection AddApplication(this IServiceCollection services) { services.AddMediatR(config => { config.RegisterServicesFromAssemblyContaining(); // Add validation behavior - runs before handler config.AddOpenBehavior(typeof(ValidationBehavior<,>)); // Add authorization behavior - runs before handler config.AddOpenBehavior(typeof(AuthorizationBehavior<,>)); }); // Register all FluentValidation validators services.AddValidatorsFromAssemblyContaining( includeInternalTypes: true); return services; } } // Authorization behavior extracts attributes and enforces authorization public class AuthorizationBehavior( IAuthorizationService authorizationService) : IPipelineBehavior where TRequest : IAuthorizeableRequest { public async Task Handle( TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { // Extract authorization attributes from request type var authorizationAttributes = request.GetType() .GetCustomAttributes(typeof(AuthorizeAttribute), true) .Cast(); // Authorize based on roles, permissions, and policies var authorizationResult = await authorizationService.AuthorizeAsync( request, authorizationAttributes); if (authorizationResult.IsError) { return (dynamic)authorizationResult.Errors; } return await next(); } } ``` -------------------------------- ### Set Reminder Command (CQRS) Source: https://context7.com/amantinband/clean-architecture/llms.txt Implements a command handler for setting a reminder using CQRS pattern with MediatR. Includes authorization, validation, and error handling. ```APIDOC ## Set Reminder Command Handler ### Description This section details the implementation of a command handler for setting a reminder within a clean architecture project using MediatR. It demonstrates declarative authorization, validation, and robust error handling. ### Language C# ### Code Example ```csharp using CleanArchitecture.Application.Common.Interfaces; using CleanArchitecture.Application.Common.Security.Permissions; using CleanArchitecture.Application.Common.Security.Policies; using CleanArchitecture.Application.Common.Security.Request; using CleanArchitecture.Domain.Reminders; using ErrorOr; using MediatR; namespace CleanArchitecture.Application.Reminders.Commands.SetReminder; // Command with declarative authorization [Authorize(Permissions = Permission.Reminder.Set, Policies = Policy.SelfOrAdmin)] public record SetReminderCommand( Guid UserId, Guid SubscriptionId, string Text, DateTime DateTime) : IAuthorizeableRequest>; // Command handler with dependency injection public class SetReminderCommandHandler( IUsersRepository usersRepository, IRemindersRepository remindersRepository) : IRequestHandler> { public async Task> Handle( SetReminderCommand command, CancellationToken cancellationToken) { // Create reminder entity var reminder = new Reminder( Guid.NewGuid(), command.UserId, command.SubscriptionId, command.Text, command.DateTime); // Get user by subscription ID var user = await usersRepository.GetBySubscriptionIdAsync( command.SubscriptionId, cancellationToken); if (user is null) { return Error.NotFound(description: "User not found"); } // Domain logic validates subscription limits var setReminderResult = user.SetReminder(reminder); if (setReminderResult.IsError) { return setReminderResult.Errors; } // Update aggregate root - domain events queued automatically await usersRepository.UpdateAsync(user, cancellationToken); return reminder; } } ``` ### Key Components - **`SetReminderCommand`**: A record representing the command to set a reminder. It includes properties for `UserId`, `SubscriptionId`, `Text`, and `DateTime`. It is decorated with `[Authorize]` to enforce security policies. - **`IAuthorizeableRequest>`**: An interface indicating that the command requires authorization and returns a result that can be either an `Error` or a `Reminder` object. - **`SetReminderCommandHandler`**: The handler class responsible for processing the `SetReminderCommand`. It depends on `IUsersRepository` and `IRemindersRepository` for data access. - **Dependency Injection**: The handler utilizes constructor injection to receive instances of the repository interfaces. - **Domain Logic**: The `user.SetReminder(reminder)` call encapsulates domain-specific logic for validating the reminder and subscription limits. - **Error Handling**: The `ErrorOr` type is used to represent success or failure, allowing for detailed error reporting (e.g., `Error.NotFound`). - **Event Handling**: Domain events are automatically queued and handled upon updating the user aggregate root. ```