# Templates Backend - Application Skill

> These templates generate C# code for new applications/modules.

---

## TEMPLATE: PERMISSIONS CLASS

```csharp
// Add to src/SmartStack.Application/Common/Authorization/Permissions.cs

public static class $APPLICATION_PASCAL
{
    public const string Access = "$application";

    public static class $MODULE_PASCAL
    {
        public const string View = "$application.$module.read";
        public const string Create = "$application.$module.create";
        public const string Update = "$application.$module.update";
        public const string Delete = "$application.$module.delete";
        public const string Assign = "$application.$module.assign";
        public const string Execute = "$application.$module.execute";
    }
}
```

### Example for Sales > Products

```csharp
public static class Sales
{
    public const string Access = "sales";

    public static class Products
    {
        public const string View = "sales.products.read";
        public const string Create = "sales.products.create";
        public const string Update = "sales.products.update";
        public const string Delete = "sales.products.delete";
        public const string Assign = "sales.products.assign";
        public const string Execute = "sales.products.execute";
    }
}
```

---

## TEMPLATE: SERVICE INTERFACE

```csharp
// src/SmartStack.Application/Common/Interfaces/I{Module}Service.cs

using SmartStack.Application.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;

namespace SmartStack.Application.Common.Interfaces;

public interface I$MODULE_PASCALService
{
    Task<PaginatedResult<$ENTITY_PASCALDto>> GetAllAsync(
        $ENTITY_PASCALQueryParameters parameters,
        CancellationToken cancellationToken = default);

    Task<$ENTITY_PASCALDto?> GetByIdAsync(
        Guid id,
        CancellationToken cancellationToken = default);

    Task<$ENTITY_PASCALDto> CreateAsync(
        Create$ENTITY_PASCALRequest request,
        CancellationToken cancellationToken = default);

    Task<$ENTITY_PASCALDto> UpdateAsync(
        Guid id,
        Update$ENTITY_PASCALRequest request,
        CancellationToken cancellationToken = default);

    Task<bool> DeleteAsync(
        Guid id,
        CancellationToken cancellationToken = default);
}
```

---

## TEMPLATE: SERVICE IMPLEMENTATION

> **TENANT ISOLATION RULES (BLOCKING):**
> - ALL queries MUST include `.Where(x => x.TenantId == _currentUser.TenantId)`
> - ALL entity creation MUST pass `_currentUser.TenantId` as first parameter to `Entity.Create()`
> - NEVER use `new Entity { }` without `TenantId =` — always prefer the factory method
> - NEVER use `Guid.Empty` as a placeholder — resolve the actual value from `_currentUser`

```csharp
// src/SmartStack.Infrastructure/Services/$APPLICATION_PASCAL/$MODULE_PASCAL/$MODULE_PASCALService.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SmartStack.Application.Common.Interfaces;
using SmartStack.Application.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
using SmartStack.Domain.$APPLICATION_PASCAL.$MODULE_PASCAL;

namespace SmartStack.Infrastructure.Services.$APPLICATION_PASCAL.$MODULE_PASCAL;

public class $MODULE_PASCALService : I$MODULE_PASCALService
{
    private readonly IApplicationDbContext _context;
    private readonly ICurrentUserService _currentUser;
    private readonly ILogger<$MODULE_PASCALService> _logger;

    public $MODULE_PASCALService(
        IApplicationDbContext context,
        ICurrentUserService currentUser,
        ILogger<$MODULE_PASCALService> logger)
    {
        _context = context;
        _currentUser = currentUser;
        _logger = logger;
    }

    public async Task<PaginatedResult<$ENTITY_PASCALDto>> GetAllAsync(
        $ENTITY_PASCALQueryParameters parameters,
        CancellationToken cancellationToken = default)
    {
        var query = _context.$ENTITY_PLURAL
            .AsNoTracking()
            .Where(x => x.TenantId == _currentUser.TenantId); // MANDATORY: tenant isolation

        // Apply filters
        if (!string.IsNullOrWhiteSpace(parameters.SearchTerm))
        {
            query = query.Where(x =>
                x.Name.Contains(parameters.SearchTerm) ||
                x.Description.Contains(parameters.SearchTerm));
        }

        // Apply sorting
        query = parameters.SortColumn?.ToLower() switch
        {
            "name" => parameters.SortDirection == "desc"
                ? query.OrderByDescending(x => x.Name)
                : query.OrderBy(x => x.Name),
            "createdat" => parameters.SortDirection == "desc"
                ? query.OrderByDescending(x => x.CreatedAt)
                : query.OrderBy(x => x.CreatedAt),
            _ => query.OrderByDescending(x => x.CreatedAt)
        };

        var totalCount = await query.CountAsync(cancellationToken);

        var items = await query
            .Skip((parameters.Page - 1) * parameters.PageSize)
            .Take(parameters.PageSize)
            .Select(x => new $ENTITY_PASCALDto
            {
                Id = x.Id,
                Name = x.Name,
                Description = x.Description,
                IsActive = x.IsActive,
                CreatedAt = x.CreatedAt,
                UpdatedAt = x.UpdatedAt
            })
            .ToListAsync(cancellationToken);

        return new PaginatedResult<$ENTITY_PASCALDto>
        {
            Items = items,
            TotalCount = totalCount,
            Page = parameters.Page,
            PageSize = parameters.PageSize
        };
    }

    public async Task<$ENTITY_PASCALDto?> GetByIdAsync(
        Guid id,
        CancellationToken cancellationToken = default)
    {
        var entity = await _context.$ENTITY_PLURAL
            .AsNoTracking()
            .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY: tenant isolation
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);

        if (entity == null)
            return null;

        return new $ENTITY_PASCALDto
        {
            Id = entity.Id,
            Name = entity.Name,
            Description = entity.Description,
            IsActive = entity.IsActive,
            CreatedAt = entity.CreatedAt,
            UpdatedAt = entity.UpdatedAt
        };
    }

    public async Task<$ENTITY_PASCALDto> CreateAsync(
        Create$ENTITY_PASCALRequest request,
        CancellationToken cancellationToken = default)
    {
        var entity = $ENTITY_PASCAL.Create(
            _currentUser.TenantId, // MANDATORY: tenant isolation — always first parameter
            request.Name,
            request.Description);

        _context.$ENTITY_PLURAL.Add(entity);
        await _context.SaveChangesAsync(cancellationToken);

        _logger.LogInformation(
            "User {User} created $ENTITY_PASCAL {Id} ({Name})",
            _currentUser.Email,
            entity.Id,
            entity.Name);

        return new $ENTITY_PASCALDto
        {
            Id = entity.Id,
            Name = entity.Name,
            Description = entity.Description,
            IsActive = entity.IsActive,
            CreatedAt = entity.CreatedAt
        };
    }

    public async Task<$ENTITY_PASCALDto> UpdateAsync(
        Guid id,
        Update$ENTITY_PASCALRequest request,
        CancellationToken cancellationToken = default)
    {
        var entity = await _context.$ENTITY_PLURAL
            .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY: tenant isolation
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken)
            ?? throw new NotFoundException("$ENTITY_PASCAL", id);

        entity.Update(request.Name, request.Description);

        await _context.SaveChangesAsync(cancellationToken);

        _logger.LogInformation(
            "User {User} updated $ENTITY_PASCAL {Id} ({Name})",
            _currentUser.Email,
            entity.Id,
            entity.Name);

        return new $ENTITY_PASCALDto
        {
            Id = entity.Id,
            Name = entity.Name,
            Description = entity.Description,
            IsActive = entity.IsActive,
            CreatedAt = entity.CreatedAt,
            UpdatedAt = entity.UpdatedAt
        };
    }

    public async Task<bool> DeleteAsync(
        Guid id,
        CancellationToken cancellationToken = default)
    {
        var entity = await _context.$ENTITY_PLURAL
            .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY: tenant isolation
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);

        if (entity == null)
            return false;

        _context.$ENTITY_PLURAL.Remove(entity);
        await _context.SaveChangesAsync(cancellationToken);

        _logger.LogWarning(
            "User {User} deleted $ENTITY_PASCAL {Id} ({Name})",
            _currentUser.Email,
            id,
            entity.Name);

        return true;
    }
}
```

---

## TEMPLATE: DTOs

```csharp
// src/SmartStack.Application/$APPLICATION_PASCAL/$MODULE_PASCAL/DTOs/$ENTITY_PASCALDto.cs

namespace SmartStack.Application.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;

public record $ENTITY_PASCALDto
{
    public Guid Id { get; init; }
    public string Name { get; init; } = null!;
    public string? Description { get; init; }
    public bool IsActive { get; init; }
    public DateTime CreatedAt { get; init; }
    public DateTime? UpdatedAt { get; init; }
}

public record Create$ENTITY_PASCALRequest
{
    public string Name { get; init; } = null!;
    public string? Description { get; init; }
}

public record Update$ENTITY_PASCALRequest
{
    public string Name { get; init; } = null!;
    public string? Description { get; init; }
}

public record $ENTITY_PASCALQueryParameters
{
    public int Page { get; init; } = 1;
    public int PageSize { get; init; } = 10;
    public string? SearchTerm { get; init; }
    public string? SortColumn { get; init; } = "createdAt";
    public string? SortDirection { get; init; } = "desc";
}
```

---

## TEMPLATE: DOMAIN ENTITY

```csharp
// src/SmartStack.Domain/$APPLICATION_PASCAL/$MODULE_PASCAL/$ENTITY_PASCAL.cs

using SmartStack.Domain.Common;

namespace SmartStack.Domain.$APPLICATION_PASCAL.$MODULE_PASCAL;

public class $ENTITY_PASCAL : BaseEntity
{
    public string Name { get; private set; } = null!;
    public string? Description { get; private set; }
    public bool IsActive { get; private set; }

    private $ENTITY_PASCAL() { }

    public static $ENTITY_PASCAL Create(string name, string? description = null)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new Exceptions.DomainException("Name is required");

        return new $ENTITY_PASCAL
        {
            Id = Guid.NewGuid(),
            Name = name,
            Description = description,
            IsActive = true,
            CreatedAt = DateTime.UtcNow
        };
    }

    public void Update(string name, string? description)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new Exceptions.DomainException("Name is required");

        Name = name;
        Description = description;
        UpdatedAt = DateTime.UtcNow;
    }

    public void Activate() => IsActive = true;
    public void Deactivate() => IsActive = false;
}
```

---

## TEMPLATE: EF CONFIGURATION

```csharp
// src/SmartStack.Infrastructure/Persistence/Configurations/$APPLICATION_PASCAL/$MODULE_PASCAL/$ENTITY_PASCALConfiguration.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SmartStack.Domain.$APPLICATION_PASCAL.$MODULE_PASCAL;

namespace SmartStack.Infrastructure.Persistence.Configurations.$APPLICATION_PASCAL.$MODULE_PASCAL;

public class $ENTITY_PASCALConfiguration : IEntityTypeConfiguration<$ENTITY_PASCAL>
{
    public void Configure(EntityTypeBuilder<$ENTITY_PASCAL> builder)
    {
        builder.ToTable("$ENTITY_PLURAL", "$SCHEMA");

        builder.HasKey(x => x.Id);

        builder.Property(x => x.Name)
            .HasMaxLength(200)
            .IsRequired();

        builder.Property(x => x.Description)
            .HasMaxLength(1000);

        builder.HasIndex(x => x.Name)
            .IsUnique();
    }
}
```

---

## TEMPLATE: DBCONTEXT UPDATE

```csharp
// Ajouter dans IApplicationDbContext.cs
DbSet<$ENTITY_PASCAL> $ENTITY_PLURAL { get; }

// Ajouter dans ApplicationDbContext.cs
public DbSet<$ENTITY_PASCAL> $ENTITY_PLURAL => Set<$ENTITY_PASCAL>();
```

---

## TEMPLATE: DI REGISTRATION

```csharp
// Ajouter dans src/SmartStack.Infrastructure/DependencyInjection.cs

services.AddScoped<I$MODULE_PASCALService, $MODULE_PASCALService>();
```

---

## CHAINING WITH /controller create

After backend templates generation, the skill automatically calls:

```
/controller create $AREA $MODULE $ENTITY
```

Example:
```
/controller create Business Products Product
```

This generates:
- Full CRUD Controller
- REST Endpoints
- ProducesResponseType
- Appropriate logging
- Postman tests

---

## BACKEND CHECKLIST

| Check | Status |
|-------|--------|
| ☐ Permissions.cs updated | |
| ☐ Service interface created | |
| ☐ Service implementation created | |
| ☐ DTOs created | |
| ☐ Domain entity created | |
| ☐ EF Configuration created | |
| ☐ DbContext updated | |
| ☐ DI registration added | |
| ☐ /controller:create executed | |
| ☐ dotnet build successful | |
