diff --git a/src/ApiHost1/ApiHost1.csproj b/src/ApiHost1/ApiHost1.csproj
index 693923f4..35787a7e 100644
--- a/src/ApiHost1/ApiHost1.csproj
+++ b/src/ApiHost1/ApiHost1.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/ApiHost1/HostedModules.cs b/src/ApiHost1/HostedModules.cs
index a78362ff..81d42da8 100644
--- a/src/ApiHost1/HostedModules.cs
+++ b/src/ApiHost1/HostedModules.cs
@@ -7,6 +7,7 @@
using ImagesInfrastructure;
using Infrastructure.Web.Hosting.Common;
using OrganizationsInfrastructure;
+using SubscriptionsInfrastructure;
using UserProfilesInfrastructure;
namespace ApiHost1;
@@ -21,6 +22,7 @@ public static SubdomainModules Get()
modules.Register(new UserProfilesModule());
modules.Register(new EndUsersModule());
modules.Register(new OrganizationsModule());
+ modules.Register(new SubscriptionsModule());
modules.Register(new IdentityModule());
modules.Register(new EventNotificationsModule());
modules.Register(new AncillaryModule());
diff --git a/src/Application.Services.Shared/ISubscriptionsService.cs b/src/Application.Services.Shared/ISubscriptionsService.cs
new file mode 100644
index 00000000..6db75749
--- /dev/null
+++ b/src/Application.Services.Shared/ISubscriptionsService.cs
@@ -0,0 +1,5 @@
+namespace Application.Services.Shared;
+
+public interface ISubscriptionsService
+{
+}
\ No newline at end of file
diff --git a/src/Domain.Events.Shared/Subscriptions/Created.cs b/src/Domain.Events.Shared/Subscriptions/Created.cs
new file mode 100644
index 00000000..0b0658b7
--- /dev/null
+++ b/src/Domain.Events.Shared/Subscriptions/Created.cs
@@ -0,0 +1,21 @@
+using Domain.Common;
+using Domain.Common.ValueObjects;
+using JetBrains.Annotations;
+
+namespace Domain.Events.Shared.Subscriptions;
+
+public sealed class Created : DomainEvent
+{
+ public Created(Identifier id) : base(id)
+ {
+ }
+
+ [UsedImplicitly]
+ public Created()
+ {
+ }
+
+ public required string CreatedById { get; set; }
+
+ public required string OrganizationId { get; set; }
+}
\ No newline at end of file
diff --git a/src/SaaStack.sln b/src/SaaStack.sln
index 43ed16fc..b083ddc3 100644
--- a/src/SaaStack.sln
+++ b/src/SaaStack.sln
@@ -376,6 +376,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventNotificationsApplicati
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Hosting.Common.UnitTests", "Infrastructure.Hosting.Common.UnitTests\Infrastructure.Hosting.Common.UnitTests.csproj", "{C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsInfrastructure", "SubscriptionsInfrastructure\SubscriptionsInfrastructure.csproj", "{AC4B3281-7555-4CDD-9574-96B73D2414D0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsApplication", "SubscriptionsApplication\SubscriptionsApplication.csproj", "{00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsDomain", "SubscriptionsDomain\SubscriptionsDomain.csproj", "{EB8FC27F-3138-4311-BF55-0590019ED5A1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1A2524F5-09D6-4774-A585-96DBAC6E9CAE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsDomain.UnitTests", "SubscriptionsDomain.UnitTests\SubscriptionsDomain.UnitTests.csproj", "{687C172C-1B65-476F-B951-DD82E3C9CB69}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsApplication.UnitTests", "SubscriptionsApplication.UnitTests\SubscriptionsApplication.UnitTests.csproj", "{BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsInfrastructure.UnitTests", "SubscriptionsInfrastructure.UnitTests\SubscriptionsInfrastructure.UnitTests.csproj", "{06282A09-852B-4628-988B-D0379FDFC4E1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsInfrastructure.IntegrationTests", "SubscriptionsInfrastructure.IntegrationTests\SubscriptionsInfrastructure.IntegrationTests.csproj", "{90055376-3EB3-4AFD-B413-1CE6EC1CC909}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1211,6 +1227,48 @@ Global
{C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}.Release|Any CPU.Build.0 = Release|Any CPU
{C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
{C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {687C172C-1B65-476F-B951-DD82E3C9CB69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {687C172C-1B65-476F-B951-DD82E3C9CB69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {687C172C-1B65-476F-B951-DD82E3C9CB69}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {687C172C-1B65-476F-B951-DD82E3C9CB69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {687C172C-1B65-476F-B951-DD82E3C9CB69}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {687C172C-1B65-476F-B951-DD82E3C9CB69}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {06282A09-852B-4628-988B-D0379FDFC4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06282A09-852B-4628-988B-D0379FDFC4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06282A09-852B-4628-988B-D0379FDFC4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06282A09-852B-4628-988B-D0379FDFC4E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06282A09-852B-4628-988B-D0379FDFC4E1}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {06282A09-852B-4628-988B-D0379FDFC4E1}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F5C77A86-38AF-40E4-82FC-617E624B2754} = {508E7DA4-4DF2-4201-955D-CCF70C41AD05}
@@ -1393,5 +1451,13 @@ Global
{41729001-D63B-4C95-AAE7-AC976EC306D2} = {E15AB9C5-C0F1-471D-BFC3-72ED133C4470}
{E1D4D16F-9F16-4EF7-8893-A43446EC1653} = {E15AB9C5-C0F1-471D-BFC3-72ED133C4470}
{C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72} = {F561CAF6-2A8C-4440-B12E-7753F25D9879}
+ {AC4B3281-7555-4CDD-9574-96B73D2414D0} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807}
+ {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807}
+ {EB8FC27F-3138-4311-BF55-0590019ED5A1} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807}
+ {1A2524F5-09D6-4774-A585-96DBAC6E9CAE} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807}
+ {687C172C-1B65-476F-B951-DD82E3C9CB69} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE}
+ {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE}
+ {06282A09-852B-4628-988B-D0379FDFC4E1} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE}
+ {90055376-3EB3-4AFD-B413-1CE6EC1CC909} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE}
EndGlobalSection
EndGlobal
diff --git a/src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj
new file mode 100644
index 00000000..ef1e7a7b
--- /dev/null
+++ b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs
new file mode 100644
index 00000000..e7fc5409
--- /dev/null
+++ b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs
@@ -0,0 +1,9 @@
+using Xunit;
+
+namespace SubscriptionsApplication.UnitTests;
+
+[Trait("Category", "Unit")]
+public class SubscriptionsApplicationSpec
+{
+ //TODO: type testm or testma to create a new test method
+}
\ No newline at end of file
diff --git a/src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs b/src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs
new file mode 100644
index 00000000..55c133af
--- /dev/null
+++ b/src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs
@@ -0,0 +1,13 @@
+using Application.Services.Shared;
+
+namespace SubscriptionsApplication.ApplicationServices;
+
+public class SubscriptionsInProcessServiceClient : ISubscriptionsService
+{
+ private readonly ISubscriptionsApplication _subscriptionsApplication;
+
+ public SubscriptionsInProcessServiceClient(ISubscriptionsApplication subscriptionsApplication)
+ {
+ _subscriptionsApplication = subscriptionsApplication;
+ }
+}
\ No newline at end of file
diff --git a/src/SubscriptionsApplication/ISubscriptionsApplication.cs b/src/SubscriptionsApplication/ISubscriptionsApplication.cs
new file mode 100644
index 00000000..97bb0ff0
--- /dev/null
+++ b/src/SubscriptionsApplication/ISubscriptionsApplication.cs
@@ -0,0 +1,5 @@
+namespace SubscriptionsApplication;
+
+public interface ISubscriptionsApplication
+{
+}
\ No newline at end of file
diff --git a/src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs b/src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs
new file mode 100644
index 00000000..2b0a031e
--- /dev/null
+++ b/src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs
@@ -0,0 +1,5 @@
+namespace SubscriptionsApplication.Persistence;
+
+public interface ISubscriptionRepository
+{
+}
\ No newline at end of file
diff --git a/src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs b/src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs
new file mode 100644
index 00000000..f84fe895
--- /dev/null
+++ b/src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs
@@ -0,0 +1,11 @@
+using Application.Persistence.Common;
+using Common;
+using QueryAny;
+
+namespace SubscriptionsApplication.Persistence.ReadModels;
+
+[EntityName("Subscription")]
+public class Subscription : ReadModelEntity
+{
+ public Optional CreatedById { get; set; }
+}
\ No newline at end of file
diff --git a/src/SubscriptionsApplication/SubscriptionsApplication.cs b/src/SubscriptionsApplication/SubscriptionsApplication.cs
new file mode 100644
index 00000000..829d1a19
--- /dev/null
+++ b/src/SubscriptionsApplication/SubscriptionsApplication.cs
@@ -0,0 +1,5 @@
+namespace SubscriptionsApplication;
+
+public class SubscriptionsApplication : ISubscriptionsApplication
+{
+}
\ No newline at end of file
diff --git a/src/SubscriptionsApplication/SubscriptionsApplication.csproj b/src/SubscriptionsApplication/SubscriptionsApplication.csproj
new file mode 100644
index 00000000..9ae0e497
--- /dev/null
+++ b/src/SubscriptionsApplication/SubscriptionsApplication.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+
+
+
+
+ <_Parameter1>$(AssemblyName).UnitTests
+
+
+
+
+
+
+
+
+
diff --git a/src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs b/src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs
new file mode 100644
index 00000000..ce8e41b2
--- /dev/null
+++ b/src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs
@@ -0,0 +1,9 @@
+using Xunit;
+
+namespace SubscriptionsDomain.UnitTests;
+
+[Trait("Category", "Unit")]
+public class SubscriptionRootSpec
+{
+ //TODO: type testm or testma to create a new test method
+}
\ No newline at end of file
diff --git a/src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj b/src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj
new file mode 100644
index 00000000..a236ff46
--- /dev/null
+++ b/src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SubscriptionsDomain/Events.cs b/src/SubscriptionsDomain/Events.cs
new file mode 100644
index 00000000..63373dd6
--- /dev/null
+++ b/src/SubscriptionsDomain/Events.cs
@@ -0,0 +1,16 @@
+using Domain.Common.ValueObjects;
+using Domain.Events.Shared.Subscriptions;
+
+namespace SubscriptionsDomain;
+
+public static class Events
+{
+ public static Created Created(Identifier id, Identifier organizationId, Identifier createdBy)
+ {
+ return new Created(id)
+ {
+ OrganizationId = organizationId,
+ CreatedById = createdBy
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/SubscriptionsDomain/Resources.Designer.cs b/src/SubscriptionsDomain/Resources.Designer.cs
new file mode 100644
index 00000000..951cced4
--- /dev/null
+++ b/src/SubscriptionsDomain/Resources.Designer.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace SubscriptionsDomain {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SubscriptionsDomain.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/src/SubscriptionsDomain/Resources.resx b/src/SubscriptionsDomain/Resources.resx
new file mode 100644
index 00000000..755958fe
--- /dev/null
+++ b/src/SubscriptionsDomain/Resources.resx
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
+ PublicKeyToken=b77a5c561934e089
+
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
+ PublicKeyToken=b77a5c561934e089
+
+
+
\ No newline at end of file
diff --git a/src/SubscriptionsDomain/SubscriptionRoot.cs b/src/SubscriptionsDomain/SubscriptionRoot.cs
new file mode 100644
index 00000000..099a9440
--- /dev/null
+++ b/src/SubscriptionsDomain/SubscriptionRoot.cs
@@ -0,0 +1,70 @@
+using Common;
+using Domain.Common.Entities;
+using Domain.Common.Identity;
+using Domain.Common.ValueObjects;
+using Domain.Events.Shared.Subscriptions;
+using Domain.Interfaces;
+using Domain.Interfaces.Entities;
+using Domain.Interfaces.ValueObjects;
+
+namespace SubscriptionsDomain;
+
+public sealed class SubscriptionRoot : AggregateRootBase
+{
+ public static Result Create(IRecorder recorder, IIdentifierFactory idFactory,
+ Identifier organizationId, Identifier createdById)
+ {
+ var root = new SubscriptionRoot(recorder, idFactory);
+ root.RaiseCreateEvent(SubscriptionsDomain.Events.Created(root.Id, organizationId, createdById));
+ return root;
+ }
+
+ private SubscriptionRoot(IRecorder recorder, IIdentifierFactory idFactory) : base(recorder, idFactory)
+ {
+ }
+
+ private SubscriptionRoot(IRecorder recorder, IIdentifierFactory idFactory,
+ ISingleValueObject identifier) : base(
+ recorder, idFactory, identifier)
+ {
+ }
+
+ public Identifier CreatedById { get; private set; } = Identifier.Empty();
+
+ public Identifier OrganizationId { get; private set; } = Identifier.Empty();
+
+ public static AggregateRootFactory Rehydrate()
+ {
+ return (identifier, container, _) => new SubscriptionRoot(container.GetRequiredService(),
+ container.GetRequiredService(), identifier);
+ }
+
+ public override Result EnsureInvariants()
+ {
+ var ensureInvariants = base.EnsureInvariants();
+ if (!ensureInvariants.IsSuccessful)
+ {
+ return ensureInvariants.Error;
+ }
+
+ //TODO: add your other invariant rules here
+
+ return Result.Ok;
+ }
+
+ protected override Result OnStateChanged(IDomainEvent @event, bool isReconstituting)
+ {
+ switch (@event)
+ {
+ case Created created:
+ {
+ OrganizationId = created.OrganizationId.ToId();
+ CreatedById = created.CreatedById.ToId();
+ return Result.Ok;
+ }
+
+ default:
+ return HandleUnKnownStateChangedEvent(@event);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SubscriptionsDomain/SubscriptionsDomain.csproj b/src/SubscriptionsDomain/SubscriptionsDomain.csproj
new file mode 100644
index 00000000..1001a123
--- /dev/null
+++ b/src/SubscriptionsDomain/SubscriptionsDomain.csproj
@@ -0,0 +1,34 @@
+
+
+
+ net8.0
+
+
+
+
+ <_Parameter1>$(AssemblyName).UnitTests
+
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
diff --git a/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs
new file mode 100644
index 00000000..1b3884b6
--- /dev/null
+++ b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs
@@ -0,0 +1,24 @@
+using ApiHost1;
+using IntegrationTesting.WebApi.Common;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace SubscriptionsInfrastructure.IntegrationTests;
+
+[Trait("Category", "Integration.API")]
+[Collection("API")]
+public class SubscriptionsApiSpec : WebApiSpec
+{
+ public SubscriptionsApiSpec(WebApiSetup setup) : base(setup, OverrideDependencies)
+ {
+ EmptyAllRepositories();
+ }
+
+ private static void OverrideDependencies(IServiceCollection services)
+ {
+ //TODO: remove this is method if you are not overriding any dependencies with any stubs
+ throw new NotImplementedException();
+ }
+
+ //TIP: type testm or testma to create a new test method
+}
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj
new file mode 100644
index 00000000..8964110a
--- /dev/null
+++ b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json b/src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json
new file mode 100644
index 00000000..1f175195
--- /dev/null
+++ b/src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json
@@ -0,0 +1,16 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "ApplicationServices": {
+ "Persistence": {
+ "LocalMachineJsonFileStore": {
+ "RootPath": "./saastack/testing/subscriptions"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs b/src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs
new file mode 100644
index 00000000..fe85e854
--- /dev/null
+++ b/src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs
@@ -0,0 +1,9 @@
+using Xunit;
+
+namespace SubscriptionsInfrastructure.UnitTests.Api.Subscriptions;
+
+[Trait("Category", "Unit")]
+public class Class1Spec
+{
+ //TODO: type testm or testma to create a new test method
+}
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj b/src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj
new file mode 100644
index 00000000..4f087f43
--- /dev/null
+++ b/src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs b/src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs
new file mode 100644
index 00000000..60a2d2ec
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs
@@ -0,0 +1,20 @@
+using Infrastructure.Interfaces;
+using Infrastructure.Web.Api.Interfaces;
+using SubscriptionsApplication;
+
+namespace SubscriptionsInfrastructure.Api.Subscriptions;
+
+public class SubscriptionsApi : IWebApiService
+{
+ private readonly ICallerContextFactory _callerFactory;
+ private readonly ISubscriptionsApplication _subscriptionsApplication;
+
+ public SubscriptionsApi(ICallerContextFactory callerFactory, ISubscriptionsApplication subscriptionsApplication)
+ {
+ _callerFactory = callerFactory;
+ _subscriptionsApplication = subscriptionsApplication;
+ }
+
+ //TODO: Add your service operation methods here
+ //Tip: try: postapi and getapi
+}
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs b/src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs
new file mode 100644
index 00000000..9ae3ca12
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs
@@ -0,0 +1,38 @@
+using Application.Persistence.Common.Extensions;
+using Application.Persistence.Interfaces;
+using Common;
+using Domain.Events.Shared.Subscriptions;
+using Domain.Interfaces;
+using Domain.Interfaces.Entities;
+using Infrastructure.Persistence.Common;
+using Infrastructure.Persistence.Interfaces;
+using SubscriptionsApplication.Persistence.ReadModels;
+using SubscriptionsDomain;
+
+namespace SubscriptionsInfrastructure.Persistence.ReadModels;
+
+public class SubscriptionProjection : IReadModelProjection
+{
+ private readonly IReadModelStore _subscriptions;
+
+ public SubscriptionProjection(IRecorder recorder, IDomainFactory domainFactory, IDataStore store)
+ {
+ _subscriptions = new ReadModelStore(recorder, domainFactory, store);
+ }
+
+ public Type RootAggregateType => typeof(SubscriptionRoot);
+
+ public async Task> ProjectEventAsync(IDomainEvent changeEvent,
+ CancellationToken cancellationToken)
+ {
+ switch (changeEvent)
+ {
+ case Created e:
+ return await _subscriptions.HandleCreateAsync(e.RootId, dto => { dto.CreatedById = e.CreatedById; },
+ cancellationToken);
+
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs b/src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs
new file mode 100644
index 00000000..5ed03c31
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs
@@ -0,0 +1,7 @@
+using SubscriptionsApplication.Persistence;
+
+namespace SubscriptionsInfrastructure.Persistence;
+
+public class SubscriptionRepository : ISubscriptionRepository
+{
+}
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure/Resources.Designer.cs b/src/SubscriptionsInfrastructure/Resources.Designer.cs
new file mode 100644
index 00000000..f3c116f3
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/Resources.Designer.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace SubscriptionsInfrastructure {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SubscriptionsInfrastructure.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/src/SubscriptionsInfrastructure/Resources.resx b/src/SubscriptionsInfrastructure/Resources.resx
new file mode 100644
index 00000000..755958fe
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/Resources.resx
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
+ PublicKeyToken=b77a5c561934e089
+
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
+ PublicKeyToken=b77a5c561934e089
+
+
+
\ No newline at end of file
diff --git a/src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj b/src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj
new file mode 100644
index 00000000..e5061771
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+ <_Parameter1>$(AssemblyName).UnitTests
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
diff --git a/src/SubscriptionsInfrastructure/SubscriptionsModule.cs b/src/SubscriptionsInfrastructure/SubscriptionsModule.cs
new file mode 100644
index 00000000..48e2b183
--- /dev/null
+++ b/src/SubscriptionsInfrastructure/SubscriptionsModule.cs
@@ -0,0 +1,56 @@
+using System.Reflection;
+using Application.Services.Shared;
+using Common;
+using Domain.Interfaces;
+using Infrastructure.Hosting.Common.Extensions;
+using Infrastructure.Persistence.Interfaces;
+using Infrastructure.Web.Hosting.Common;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using SubscriptionsApplication;
+using SubscriptionsApplication.ApplicationServices;
+using SubscriptionsApplication.Persistence;
+using SubscriptionsDomain;
+using SubscriptionsInfrastructure.Api.Subscriptions;
+using SubscriptionsInfrastructure.Persistence;
+using SubscriptionsInfrastructure.Persistence.ReadModels;
+
+namespace SubscriptionsInfrastructure;
+
+public class SubscriptionsModule : ISubdomainModule
+{
+ public Assembly InfrastructureAssembly => typeof(SubscriptionsApi).Assembly;
+
+ public Assembly? DomainAssembly => typeof(SubscriptionRoot).Assembly;
+
+ public Dictionary EntityPrefixes => new()
+ {
+ { typeof(SubscriptionRoot), "subscript" }
+ };
+
+ public Action> ConfigureMiddleware
+ {
+ get { return (app, _) => app.RegisterRoutes(); }
+ }
+
+ public Action RegisterServices
+ {
+ get
+ {
+ return (_, services) =>
+ {
+ services
+ .AddPerHttpRequest();
+ services.AddPerHttpRequest();
+ services.RegisterEventing(
+ c => new SubscriptionProjection(c.GetRequiredService(),
+ c.GetRequiredService(),
+ c.GetRequiredService())
+ );
+
+ services.AddPerHttpRequest();
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools.Templates/InfrastructureProject/.template.config/template.json b/src/Tools.Templates/InfrastructureProject/.template.config/template.json
index 4a8db2ab..8a382aa9 100644
--- a/src/Tools.Templates/InfrastructureProject/.template.config/template.json
+++ b/src/Tools.Templates/InfrastructureProject/.template.config/template.json
@@ -20,8 +20,9 @@
"type": "parameter",
"datatype": "string",
"isRequired": true,
- "description": "Name of the subdomain (singular, Title-cased)",
- "replaces": "{SubDomainName}"
+ "description": "Name of the subdomain (Singular, Title-cased)",
+ "replaces": "{SubDomainName}",
+ "fileRename": "{SubDomainName}"
},
"SubDomainNameLower": {
"type": "generated",
diff --git a/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs b/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs
index 3d73619f..7d834bd8 100644
--- a/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs
+++ b/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs
@@ -1,15 +1,19 @@
+using Infrastructure.Interfaces;
+using Infrastructure.Web.Api.Interfaces;
+
namespace ProjectName.Api.{SubDomainName}s;
-public class {SubDomainName}sApi : IwebApiService
+public class {SubDomainName}sApi : IWebApiService
{
private readonly I{SubDomainName}sApplication _{SubDomainNameLower}sApplication;
private readonly ICallerContextFactory _callerFactory;
- public CarsApi(ICallerContextFactory callerFactory, I{SubDomainName}sApplication {SubDomainNameLower}sApplication)
+ public {SubDomainName}sApi(ICallerContextFactory callerFactory, I{SubDomainName}sApplication {SubDomainNameLower}sApplication)
{
_callerFactory = callerFactory;
_{SubDomainNameLower}sApplication = {SubDomainNameLower}sApplication;
}
//TODO: Add your service operation methods here
+ //Tip: try: postapi and getapi
}
\ No newline at end of file
diff --git a/src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs b/src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs
new file mode 100644
index 00000000..df361e86
--- /dev/null
+++ b/src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs
@@ -0,0 +1,40 @@
+using Application.Persistence.Common.Extensions;
+using Application.Persistence.Interfaces;
+using Common;
+using Domain.Interfaces;
+using Domain.Interfaces.Entities;
+using {SubDomainName}Application.Persistence.ReadModels;
+using {SubDomainName}Domain;
+using Infrastructure.Persistence.Common;
+using Infrastructure.Persistence.Interfaces;
+
+namespace ProjectName.Persistence.ReadModels;
+
+public class {SubDomainName}Projection : IReadModelProjection
+{
+ private readonly IReadModelStore<{SubDomainName}> _{SubDomainNameLower}s;
+
+ public {SubDomainName}Projection(IRecorder recorder, IDomainFactory domainFactory, IDataStore store)
+ {
+ _{SubDomainNameLower}s = new ReadModelStore<{SubDomainName}>(recorder, domainFactory, store);
+ }
+
+ public Type RootAggregateType => typeof({SubDomainName}Root);
+
+ public async Task> ProjectEventAsync(IDomainEvent changeEvent,
+ CancellationToken cancellationToken)
+ {
+ switch (changeEvent)
+ {
+ case Created e:
+ return await _{SubDomainNameLower}s.HandleCreateAsync(e.RootId, dto =>
+ {
+ dto.UserId = e.UserId;
+ },
+ cancellationToken);
+
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools.Templates/InfrastructureProject/ProjectNameModule.cs b/src/Tools.Templates/InfrastructureProject/{SubDomainName}sModule.cs
similarity index 79%
rename from src/Tools.Templates/InfrastructureProject/ProjectNameModule.cs
rename to src/Tools.Templates/InfrastructureProject/{SubDomainName}sModule.cs
index 5712dea8..a5afb55c 100644
--- a/src/Tools.Templates/InfrastructureProject/ProjectNameModule.cs
+++ b/src/Tools.Templates/InfrastructureProject/{SubDomainName}sModule.cs
@@ -1,24 +1,23 @@
using System.Reflection;
-using Application.Interfaces.Services;
-using Infrastructure.Web.Hosting.Common;
-using Common;
using Domain.Interfaces;
+using Infrastructure.Hosting.Common.Extensions;
using Infrastructure.Persistence.Interfaces;
using Infrastructure.Web.Hosting.Common;
-using Infrastructure.Web.Hosting.Common.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using ProjectName.Api.Subscriptions;
+using ProjectName.Persistence;
namespace ProjectName;
-public class {SubDomainName}sModule : ISubDomainModule
+public class {SubDomainName}sModule : ISubdomainModule
{
- public Assembly ApiAssembly => typeof({SubDomainName}sApi).Assembly;
+ public Assembly InfrastructureAssembly => typeof({SubDomainName}sApi).Assembly;
public Assembly? DomainAssembly => typeof({SubDomainName}Root).Assembly;
- public Dictionary AggregatePrefixes => new()
+ public Dictionary EntityPrefixes => new()
{
{ typeof({SubDomainName}Root), "{SubDomainNameLower}" }
};