diff --git a/CampusWorkshops.Tests/CampusWorkshops.Tests.csproj b/CampusWorkshops.Tests/CampusWorkshops.Tests.csproj
new file mode 100644
index 0000000..c6732f0
--- /dev/null
+++ b/CampusWorkshops.Tests/CampusWorkshops.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CampusWorkshops.Tests/Registration/WorkshopRegistrationPolicyTests.cs b/CampusWorkshops.Tests/Registration/WorkshopRegistrationPolicyTests.cs
new file mode 100644
index 0000000..6bef89f
--- /dev/null
+++ b/CampusWorkshops.Tests/Registration/WorkshopRegistrationPolicyTests.cs
@@ -0,0 +1,101 @@
+
+// CampusWorkshops.Tests/Registration/RegistrationPolicyTests.cs
+using System;
+using CampusWorkshops.Api.Domain.Registration;
+using CampusWorkshops.Api.Models; // Workshop do seu projeto
+using Microsoft.Extensions.Time.Testing;
+using Xunit;
+
+namespace CampusWorkshops.Tests.Registration
+{
+ public class RegistrationPolicyTests
+ {
+ private static (RegistrationPolicy Policy, FakeTimeProvider Time) BuildSut(DateTimeOffset nowUtc)
+ {
+ var time = new FakeTimeProvider(nowUtc.UtcDateTime);
+ var policy = new RegistrationPolicy(time);
+ return (policy, time);
+ }
+
+ private static Workshop W(int capacity, DateTimeOffset startAt)
+ => new Workshop
+ {
+ Id = Guid.NewGuid(),
+ Title = "Qualquer título",
+ StartAt = startAt.UtcDateTime, // se seu StartAt for DateTimeOffset, use .StartAt = startAt
+ EndAt = startAt.UtcDateTime.AddHours(2),
+ Capacity = capacity,
+ IsOnline = true
+ };
+
+ [Fact]
+ public void Rejects_When_Window_Closed_At_24h()
+ {
+ // Arrange
+ var now = DateTimeOffset.Parse("2025-03-01T12:00:00Z");
+ var (policy, _) = BuildSut(now);
+ var w = W(capacity: 10, startAt: now.AddHours(RegistrationPolicy.CloseWindowLeadHours));
+ var enrolled = 0;
+
+ // Act
+ var decision = policy.Decide(w, enrolled);
+
+ // Assert
+ Assert.Equal(RegistrationOutcome.Rejected, decision.Outcome);
+ Assert.Contains("Registration window closed.", decision.Reasons);
+ }
+
+ [Fact]
+ public void Accepts_When_Seats_Available()
+ {
+ // Arrange
+ var now = DateTimeOffset.Parse("2025-03-01T12:00:00Z");
+ var (policy, _) = BuildSut(now);
+ var w = W(capacity: 10, startAt: now.AddHours(36));
+ var enrolled = 9;
+
+ // Act
+ var decision = policy.Decide(w, enrolled);
+
+ // Assert
+ Assert.Equal(RegistrationOutcome.Accepted, decision.Outcome);
+ Assert.Contains("Accepted", decision.Reasons);
+ }
+
+ [Fact]
+ public void Rejects_When_Full()
+ {
+ // Arrange
+ var now = DateTimeOffset.Parse("2025-03-01T12:00:00Z");
+ var (policy, _) = BuildSut(now);
+ var w = W(capacity: 10, startAt: now.AddHours(36));
+ var enrolled = 10;
+
+ // Act
+ var decision = policy.Decide(w, enrolled);
+
+ // Assert
+ Assert.Equal(RegistrationOutcome.Rejected, decision.Outcome);
+ Assert.Contains("No seats available.", decision.Reasons);
+ }
+
+ [Theory]
+ [InlineData(30, RegistrationOutcome.Accepted)]
+ [InlineData(24, RegistrationOutcome.Rejected)]
+ [InlineData(10, RegistrationOutcome.Rejected)]
+ public void Window_Rules(int hoursToStart, RegistrationOutcome expected)
+ {
+ // Arrange
+ var now = DateTimeOffset.Parse("2025-03-01T12:00:00Z");
+ var (policy, _) = BuildSut(now);
+ var w = W(capacity: 5, startAt: now.AddHours(hoursToStart));
+ var enrolled = 0;
+
+ // Act
+ var decision = policy.Decide(w, enrolled);
+
+ // Assert
+ Assert.Equal(expected, decision.Outcome);
+ }
+ }
+}
diff --git a/CampusWorkshops.Tests/UnitTest1.cs b/CampusWorkshops.Tests/UnitTest1.cs
new file mode 100644
index 0000000..f4cce5f
--- /dev/null
+++ b/CampusWorkshops.Tests/UnitTest1.cs
@@ -0,0 +1,10 @@
+namespace CampusWorkshops.Tests;
+
+public class UnitTest1
+{
+ [Fact]
+ public void Test1()
+ {
+
+ }
+}
diff --git a/Workshop10-API.sln b/Workshop10-API.sln
index fa2520f..8dbea0f 100644
--- a/Workshop10-API.sln
+++ b/Workshop10-API.sln
@@ -1,19 +1,46 @@
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workshop10-API", "Workshop10-API.csproj", "{5106011C-9EE5-2CD0-05A5-164233706862}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workshop10-API", "Workshop10-API\Workshop10-API.csproj", "{A51E3052-20C8-4154-905F-4AE95A131FB1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CampusWorkshops.Tests", "CampusWorkshops.Tests\CampusWorkshops.Tests.csproj", "{3327A641-9CFC-40DA-9E4E-0E6BD3390068}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {5106011C-9EE5-2CD0-05A5-164233706862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5106011C-9EE5-2CD0-05A5-164233706862}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5106011C-9EE5-2CD0-05A5-164233706862}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5106011C-9EE5-2CD0-05A5-164233706862}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Debug|x64.Build.0 = Debug|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Debug|x86.Build.0 = Debug|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Release|x64.ActiveCfg = Release|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Release|x64.Build.0 = Release|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Release|x86.ActiveCfg = Release|Any CPU
+ {A51E3052-20C8-4154-905F-4AE95A131FB1}.Release|x86.Build.0 = Release|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Debug|x64.Build.0 = Debug|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Debug|x86.Build.0 = Debug|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Release|x64.ActiveCfg = Release|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Release|x64.Build.0 = Release|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Release|x86.ActiveCfg = Release|Any CPU
+ {3327A641-9CFC-40DA-9E4E-0E6BD3390068}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Controllers/AuthController.cs b/Workshop10-API/Controllers/AuthController.cs
similarity index 100%
rename from Controllers/AuthController.cs
rename to Workshop10-API/Controllers/AuthController.cs
diff --git a/Controllers/DiDemoController.cs b/Workshop10-API/Controllers/DiDemoController.cs
similarity index 100%
rename from Controllers/DiDemoController.cs
rename to Workshop10-API/Controllers/DiDemoController.cs
diff --git a/Controllers/WorkshopsController.cs b/Workshop10-API/Controllers/WorkshopsController.cs
similarity index 100%
rename from Controllers/WorkshopsController.cs
rename to Workshop10-API/Controllers/WorkshopsController.cs
diff --git a/Workshop10-API/Domain/Registration/WorkshopRegistrationPolicy.cs b/Workshop10-API/Domain/Registration/WorkshopRegistrationPolicy.cs
new file mode 100644
index 0000000..9c041df
--- /dev/null
+++ b/Workshop10-API/Domain/Registration/WorkshopRegistrationPolicy.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using CampusWorkshops.Api.Models; // <-- Workshop do seu projeto
+
+namespace CampusWorkshops.Api.Domain.Registration
+{
+ public enum RegistrationOutcome { Accepted, Rejected }
+
+ public sealed record RegistrationDecision(RegistrationOutcome Outcome, IReadOnlyList Reasons);
+
+ ///
+ /// Regra simples de registro para Workshop.
+ /// Regras:
+ /// - Janela fecha quando faltar <= 24h para o StartAt.
+ /// - Se há assentos (Capacity - enrolled > 0) → Accepted; senão → Rejected.
+ ///
+ public sealed class RegistrationPolicy
+ {
+ public const int CloseWindowLeadHours = 24;
+
+ private readonly TimeProvider _time;
+
+ public RegistrationPolicy(TimeProvider time)
+ {
+ _time = time ?? throw new ArgumentNullException(nameof(time));
+ }
+
+ ///
+ /// Decide usando apenas o Workshop e o número ATUAL de inscritos (enrolled).
+ ///
+ public RegistrationDecision Decide(Workshop w, int enrolled)
+ {
+ if (w is null) throw new ArgumentNullException(nameof(w));
+ var reasons = new List();
+
+ // (1) Janela de inscrição
+ var now = _time.GetUtcNow();
+ // Se StartAt for DateTime (não Offset) no seu modelo, troque por:
+ // var startAtUtc = DateTime.SpecifyKind(w.StartAt, DateTimeKind.Utc);
+ // if (startAtUtc - now <= TimeSpan.FromHours(CloseWindowLeadHours)) { ... }
+ if (w.StartAt - now <= TimeSpan.FromHours(CloseWindowLeadHours))
+ {
+ reasons.Add("Registration window closed.");
+ return new RegistrationDecision(RegistrationOutcome.Rejected, reasons);
+ }
+
+ // (2) Capacidade
+ var seatsLeft = w.Capacity - enrolled;
+ if (seatsLeft > 0)
+ {
+ reasons.Add("Accepted");
+ return new RegistrationDecision(RegistrationOutcome.Accepted, reasons);
+ }
+
+ reasons.Add("No seats available.");
+ return new RegistrationDecision(RegistrationOutcome.Rejected, reasons);
+ }
+ }
+}
+
diff --git a/Dtos/CreateWorkshopRequest.cs b/Workshop10-API/Dtos/CreateWorkshopRequest.cs
similarity index 100%
rename from Dtos/CreateWorkshopRequest.cs
rename to Workshop10-API/Dtos/CreateWorkshopRequest.cs
diff --git a/Dtos/PatchWorkshopRequest.cs b/Workshop10-API/Dtos/PatchWorkshopRequest.cs
similarity index 100%
rename from Dtos/PatchWorkshopRequest.cs
rename to Workshop10-API/Dtos/PatchWorkshopRequest.cs
diff --git a/Dtos/WorkshopResponse.cs b/Workshop10-API/Dtos/WorkshopResponse.cs
similarity index 100%
rename from Dtos/WorkshopResponse.cs
rename to Workshop10-API/Dtos/WorkshopResponse.cs
diff --git a/Infraestructure/Data/WorkshopDBContext.cs b/Workshop10-API/Infraestructure/Data/WorkshopDBContext.cs
similarity index 100%
rename from Infraestructure/Data/WorkshopDBContext.cs
rename to Workshop10-API/Infraestructure/Data/WorkshopDBContext.cs
diff --git a/Migrations/20250903175616_InitialCreate.Designer.cs b/Workshop10-API/Migrations/20250903175616_InitialCreate.Designer.cs
similarity index 100%
rename from Migrations/20250903175616_InitialCreate.Designer.cs
rename to Workshop10-API/Migrations/20250903175616_InitialCreate.Designer.cs
diff --git a/Migrations/20250903175616_InitialCreate.cs b/Workshop10-API/Migrations/20250903175616_InitialCreate.cs
similarity index 100%
rename from Migrations/20250903175616_InitialCreate.cs
rename to Workshop10-API/Migrations/20250903175616_InitialCreate.cs
diff --git a/Migrations/WorkshopsDbContextModelSnapshot.cs b/Workshop10-API/Migrations/WorkshopsDbContextModelSnapshot.cs
similarity index 100%
rename from Migrations/WorkshopsDbContextModelSnapshot.cs
rename to Workshop10-API/Migrations/WorkshopsDbContextModelSnapshot.cs
diff --git a/Models/Workshop.cs b/Workshop10-API/Models/Workshop.cs
similarity index 100%
rename from Models/Workshop.cs
rename to Workshop10-API/Models/Workshop.cs
diff --git a/Program.cs b/Workshop10-API/Program.cs
similarity index 92%
rename from Program.cs
rename to Workshop10-API/Program.cs
index ef094a0..933e153 100644
--- a/Program.cs
+++ b/Workshop10-API/Program.cs
@@ -11,6 +11,7 @@ using Microsoft.OpenApi.Models;
using CampusWorkshops.Api.Services;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Caching.Memory;
+using CampusWorkshops.Api.Domain.Registration;
var builder = WebApplication.CreateBuilder(args);
@@ -54,6 +55,11 @@ builder.Services.Configure(builder.Configuration.GetSection("Cach
// Primeiro registramos a implementação EF concreta
builder.Services.AddScoped();
+// tempo do sistema para produção:
+builder.Services.AddSingleton(TimeProvider.System);
+// regra de domínio (stateless); pode ser Singleton
+builder.Services.AddSingleton();
+
// Depois expomos IWorkshopRepository como o "EF envelopado por cache"
builder.Services.AddScoped(sp =>
new CachedWorkshopRepository(
diff --git a/Properties/launchSettings.json b/Workshop10-API/Properties/launchSettings.json
similarity index 100%
rename from Properties/launchSettings.json
rename to Workshop10-API/Properties/launchSettings.json
diff --git a/Repositories/CachedWorkshopRepository.cs b/Workshop10-API/Repositories/CachedWorkshopRepository.cs
similarity index 100%
rename from Repositories/CachedWorkshopRepository.cs
rename to Workshop10-API/Repositories/CachedWorkshopRepository.cs
diff --git a/Repositories/EfWorkshopRepository.cs b/Workshop10-API/Repositories/EfWorkshopRepository.cs
similarity index 100%
rename from Repositories/EfWorkshopRepository.cs
rename to Workshop10-API/Repositories/EfWorkshopRepository.cs
diff --git a/Repositories/IWorkshopRepository.cs b/Workshop10-API/Repositories/IWorkshopRepository.cs
similarity index 100%
rename from Repositories/IWorkshopRepository.cs
rename to Workshop10-API/Repositories/IWorkshopRepository.cs
diff --git a/Repositories/InMemoryWorkshopRepository.cs b/Workshop10-API/Repositories/InMemoryWorkshopRepository.cs
similarity index 100%
rename from Repositories/InMemoryWorkshopRepository.cs
rename to Workshop10-API/Repositories/InMemoryWorkshopRepository.cs
diff --git a/Services/CacheSettings.cs b/Workshop10-API/Services/CacheSettings.cs
similarity index 100%
rename from Services/CacheSettings.cs
rename to Workshop10-API/Services/CacheSettings.cs
diff --git a/Services/PerRequestClock.cs b/Workshop10-API/Services/PerRequestClock.cs
similarity index 100%
rename from Services/PerRequestClock.cs
rename to Workshop10-API/Services/PerRequestClock.cs
diff --git a/Services/ReportingSingleton.cs b/Workshop10-API/Services/ReportingSingleton.cs
similarity index 100%
rename from Services/ReportingSingleton.cs
rename to Workshop10-API/Services/ReportingSingleton.cs
diff --git a/Services/RequestId.cs b/Workshop10-API/Services/RequestId.cs
similarity index 100%
rename from Services/RequestId.cs
rename to Workshop10-API/Services/RequestId.cs
diff --git a/Workshop10-API.csproj b/Workshop10-API/Workshop10-API.csproj
similarity index 100%
rename from Workshop10-API.csproj
rename to Workshop10-API/Workshop10-API.csproj
diff --git a/Workshop10-API.http b/Workshop10-API/Workshop10-API.http
similarity index 100%
rename from Workshop10-API.http
rename to Workshop10-API/Workshop10-API.http
diff --git a/appsettings.Development.json b/Workshop10-API/appsettings.Development.json
similarity index 100%
rename from appsettings.Development.json
rename to Workshop10-API/appsettings.Development.json
diff --git a/appsettings.json b/Workshop10-API/appsettings.json
similarity index 100%
rename from appsettings.json
rename to Workshop10-API/appsettings.json
diff --git a/workshops.db b/Workshop10-API/workshops.db
similarity index 100%
rename from workshops.db
rename to Workshop10-API/workshops.db