diff --git a/.nuget/nuget.config b/.nuget/nuget.config new file mode 100644 index 000000000..da0fdaeeb --- /dev/null +++ b/.nuget/nuget.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..eba65de8f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,29 @@ +{ + // Controls if the editor shows reference information for the modes that support it + "editor.referenceInfos": false, + + // When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents. + "editor.detectIndentation": false, + + // Typescript version from local package to be consistent + "typescript.tsdk": "node_modules/typescript/lib", + + // Configure glob patterns for excluding files and folders. + "files.exclude": { + "_test-output": true, + "**/node_modules": true, + "**/artifacts": true, + "**/build": true, + "**/out": true, + "**/obj": true, + "**/bin": true, + "**/*.lock.json": true, + "**/*.bat": true, + "**/*.sln": true, + "**/*.sln.DotSettings": true, + "**/*.user": true, + "**/*.xproj": true, + "**/*.gitattributes": true, + ".vs:": true + } +} \ No newline at end of file diff --git a/PinkParrot.sln b/PinkParrot.sln new file mode 100644 index 000000000..6296fae50 --- /dev/null +++ b/PinkParrot.sln @@ -0,0 +1,82 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{24A3171D-2905-49C9-8A49-A473799014E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4B5539DB-F68E-4DBA-B22A-72B9FE7FE2D8}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot", "src\PinkParrot\PinkParrot.xproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pinkparrot_infrastructure", "pinkparrot_infrastructure", "{8CF53B92-5EB1-461D-98F8-70DA9B603FBF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pinkparrot_core", "pinkparrot_core", "{4C6B06C2-6D77-4E0E-AE32-D7050236433A}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot.Core.Tests", "src\pinkparrot_core\PinkParrot.Core.Tests\PinkParrot.Core.Tests.xproj", "{4A27B9DE-F553-4A82-B866-A29EF8A5A0AF}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot.Core", "src\pinkparrot_core\PinkParrot.Core\PinkParrot.Core.xproj", "{47F3C27E-698B-4EDF-A7E8-D7F4232AFBB0}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot.Infrastructure.Tests", "src\pinkparrot_infrastructure\PinkParrot.Infrastructure.Tests\PinkParrot.Infrastructure.Tests.xproj", "{840C02B1-48F8-4C8A-8862-8A3FDEFDE8D5}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot.Infrastructure", "src\pinkparrot_infrastructure\PinkParrot.Infrastructure\PinkParrot.Infrastructure.xproj", "{BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pinkparrot_events", "pinkparrot_events", "{6AE39761-FD74-45CD-99CF-73D3D2E5D064}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot.Events", "src\pinkparrot_events\PinkParrot.Events\PinkParrot.Events.xproj", "{25F66C64-058A-4D44-BC0C-F12A054F9A91}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pinkparrot_write", "pinkparrot_write", "{4AED438F-684F-4FAE-B016-21CF2EAEA79F}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PinkParrot.Write", "src\pinkparrot_write\PinkParrot.Write\PinkParrot.Write.xproj", "{A85201C6-6AF8-4B63-8365-08F741050438}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {61F6BBCE-A080-4400-B194-70E2F5D2096E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61F6BBCE-A080-4400-B194-70E2F5D2096E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61F6BBCE-A080-4400-B194-70E2F5D2096E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61F6BBCE-A080-4400-B194-70E2F5D2096E}.Release|Any CPU.Build.0 = Release|Any CPU + {4A27B9DE-F553-4A82-B866-A29EF8A5A0AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A27B9DE-F553-4A82-B866-A29EF8A5A0AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A27B9DE-F553-4A82-B866-A29EF8A5A0AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A27B9DE-F553-4A82-B866-A29EF8A5A0AF}.Release|Any CPU.Build.0 = Release|Any CPU + {47F3C27E-698B-4EDF-A7E8-D7F4232AFBB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47F3C27E-698B-4EDF-A7E8-D7F4232AFBB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47F3C27E-698B-4EDF-A7E8-D7F4232AFBB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47F3C27E-698B-4EDF-A7E8-D7F4232AFBB0}.Release|Any CPU.Build.0 = Release|Any CPU + {840C02B1-48F8-4C8A-8862-8A3FDEFDE8D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {840C02B1-48F8-4C8A-8862-8A3FDEFDE8D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {840C02B1-48F8-4C8A-8862-8A3FDEFDE8D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {840C02B1-48F8-4C8A-8862-8A3FDEFDE8D5}.Release|Any CPU.Build.0 = Release|Any CPU + {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C}.Release|Any CPU.Build.0 = Release|Any CPU + {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Release|Any CPU.Build.0 = Release|Any CPU + {A85201C6-6AF8-4B63-8365-08F741050438}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A85201C6-6AF8-4B63-8365-08F741050438}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A85201C6-6AF8-4B63-8365-08F741050438}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A85201C6-6AF8-4B63-8365-08F741050438}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {61F6BBCE-A080-4400-B194-70E2F5D2096E} = {24A3171D-2905-49C9-8A49-A473799014E8} + {4A27B9DE-F553-4A82-B866-A29EF8A5A0AF} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} + {47F3C27E-698B-4EDF-A7E8-D7F4232AFBB0} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} + {840C02B1-48F8-4C8A-8862-8A3FDEFDE8D5} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} + {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} + {25F66C64-058A-4D44-BC0C-F12A054F9A91} = {6AE39761-FD74-45CD-99CF-73D3D2E5D064} + {A85201C6-6AF8-4B63-8365-08F741050438} = {4AED438F-684F-4FAE-B016-21CF2EAEA79F} + EndGlobalSection +EndGlobal diff --git a/PinkParrot.sln.DotSettings b/PinkParrot.sln.DotSettings new file mode 100644 index 000000000..44e2de54f --- /dev/null +++ b/PinkParrot.sln.DotSettings @@ -0,0 +1,84 @@ + + False + True + False + False + True + True + True + True + True + False + True + + + ExplicitlyExcluded + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + SUGGESTION + SUGGESTION + SUGGESTION + DO_NOT_SHOW + TypeScript16 + <?xml version="1.0" encoding="utf-16"?><Profile name="Namespaces"><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Typescript"><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs></Profile> + + SingleQuoted + ========================================================================== + $FILENAME$ + PinkParrot Headless CMS +========================================================================== + Copyright (c) PinkParrot Group + All rights reserved. +========================================================================== + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 000000000..60ab9b437 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src" ], + "sdk": { + "version": "1.0.0-preview2-003121" + } +} diff --git a/src/PinkParrot/PinkParrot.xproj b/src/PinkParrot/PinkParrot.xproj new file mode 100644 index 000000000..f525b6954 --- /dev/null +++ b/src/PinkParrot/PinkParrot.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 61f6bbce-a080-4400-b194-70e2f5d2096e + PinkParrot + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + + + + + diff --git a/src/PinkParrot/Program.cs b/src/PinkParrot/Program.cs new file mode 100644 index 000000000..7f88eb8b5 --- /dev/null +++ b/src/PinkParrot/Program.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Program.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.IO; +using Microsoft.AspNetCore.Hosting; + +namespace PinkParrot +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/src/PinkParrot/Project_Readme.html b/src/PinkParrot/Project_Readme.html new file mode 100644 index 000000000..1a0f5b51a --- /dev/null +++ b/src/PinkParrot/Project_Readme.html @@ -0,0 +1,187 @@ + + + + + Welcome to ASP.NET Core + + + + + + +
+
+

This application consists of:

+
    +
  • Sample pages using ASP.NET Core MVC
  • +
  • Bower for managing client-side libraries
  • +
  • Theming using Bootstrap
  • +
+
+ + + + + +
+ + + diff --git a/src/PinkParrot/Properties/launchSettings.json b/src/PinkParrot/Properties/launchSettings.json new file mode 100644 index 000000000..a19c657ab --- /dev/null +++ b/src/PinkParrot/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:65351/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "PinkParrot": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/PinkParrot/Startup.cs b/src/PinkParrot/Startup.cs new file mode 100644 index 000000000..3bcbdb1a0 --- /dev/null +++ b/src/PinkParrot/Startup.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// Startup.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace PinkParrot +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.Run(async (context) => + { + await context.Response.WriteAsync("Hello World!"); + }); + } + } +} diff --git a/src/PinkParrot/project.json b/src/PinkParrot/project.json new file mode 100644 index 000000000..361cfdacb --- /dev/null +++ b/src/PinkParrot/project.json @@ -0,0 +1,48 @@ +{ + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + }, + "Microsoft.AspNetCore.Diagnostics": "1.0.0", + + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0" + }, + + "tools": { + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dotnet5.6", + "portable-net45+win8" + ] + } + }, + + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + + "runtimeOptions": { + "configProperties": { + "System.GC.Server": true + } + }, + + "publishOptions": { + "include": [ + "wwwroot", + "web.config" + ] + }, + + "scripts": { + "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + } +} diff --git a/src/PinkParrot/web.config b/src/PinkParrot/web.config new file mode 100644 index 000000000..dc0514fca --- /dev/null +++ b/src/PinkParrot/web.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/pinkparrot_core/PinkParrot.Core.Tests/PinkParrot.Core.Tests.xproj b/src/pinkparrot_core/PinkParrot.Core.Tests/PinkParrot.Core.Tests.xproj new file mode 100644 index 000000000..1290e1f9d --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core.Tests/PinkParrot.Core.Tests.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4a27b9de-f553-4a82-b866-a29ef8a5a0af + PinkParrot.Core.Tests + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core.Tests/project.json b/src/pinkparrot_core/PinkParrot.Core.Tests/project.json new file mode 100644 index 000000000..d06272be5 --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core.Tests/project.json @@ -0,0 +1,29 @@ +{ + "version": "1.0.0-*", + "testRunner": "xunit", + "dependencies": { + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "PinkParrot.Core": "1.0.0-*", + "xunit": "2.2.0-beta2-build3300" + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + "buildOptions": { + "copyToOutput": { + "include": [ + "xunit.runner.json" + ] + } + }, + "tooling": { + "defaultNamespace": "PinkParrot.Core.Tests" + } +} \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core.Tests/xunit.runner.json b/src/pinkparrot_core/PinkParrot.Core.Tests/xunit.runner.json new file mode 100644 index 000000000..6b3f1f87d --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core.Tests/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "diagnosticMessages": false, + "methodDisplay": "classAndMethod", + "parallelizeTestCollections": true +} diff --git a/src/pinkparrot_core/PinkParrot.Core/PinkParrot.Core.xproj b/src/pinkparrot_core/PinkParrot.Core/PinkParrot.Core.xproj new file mode 100644 index 000000000..8d0554b61 --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/PinkParrot.Core.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 47f3c27e-698b-4edf-a7e8-d7f4232afbb0 + PinkParrot.Core + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs new file mode 100644 index 000000000..816f6b296 --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs @@ -0,0 +1,200 @@ +// ========================================================================== +// ModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.Tasks; + +// ReSharper disable ConvertIfStatementToReturnStatement + +namespace PinkParrot.Core.Schema +{ + public abstract class ModelField + { + private readonly Guid id; + private string name; + private string hint; + private string displayName; + private bool isRequired; + private bool isDisabled; + private bool isHidden; + + public Guid Id + { + get { return id; } + } + + public string Name + { + get { return name; } + } + + public string Hint + { + get { return hint; } + } + + public string DisplayName + { + get { return displayName; } + } + + public bool IsRequired + { + get { return isRequired; } + } + + public bool IsHidden + { + get { return isHidden; } + } + + public bool IsDisabled + { + get { return isDisabled; } + } + + protected ModelField(Guid id, string name) + { + Guard.NotEmpty(id, nameof(id)); + Guard.ValidSlug(name, nameof(name)); + + this.id = id; + + this.name = name; + } + + public ModelField Configure(PropertiesBag settings, ICollection errors) + { + var clone = Clone(); + + if (settings.Contains("Name")) + { + clone.name = settings["Name"].ToString(); + + if (!clone.name.IsSlug()) + { + errors.Add("Field name must be a slug"); + } + } + + if (settings.Contains("Hint")) + { + clone.hint = settings["Hint"].ToString()?.Trim() ?? string.Empty; + } + + if (settings.Contains("DisplayName")) + { + clone.displayName = settings["DisplayName"].ToString()?.Trim() ?? string.Empty; + } + + if (settings.Contains("IsRequired")) + { + try + { + clone.isRequired = settings["IsRequired"].ToBoolean(CultureInfo.InvariantCulture); + } + catch (InvalidCastException) + { + errors.Add("IsRequired is not a valid boolean"); + } + } + + clone.ConfigureCore(settings, errors); + + return clone; + } + + protected virtual void ConfigureCore(PropertiesBag settings, ICollection errors) + { + } + + public Task ValidateAsync(PropertyValue property, ICollection errors) + { + Guard.NotNull(property, nameof(property)); + + if (isRequired && property.RawValue == null) + { + errors.Add(" is required"); + } + + if (property.RawValue == null) + { + return TaskHelper.Done; + } + + return ValidateCoreAsync(property, errors); + } + + protected virtual Task ValidateCoreAsync(PropertyValue property, ICollection errors) + { + return TaskHelper.Done; + } + + public ModelField Hide() + { + if (isHidden) + { + throw new DomainValidationException($"The field '{name} is already hidden."); + } + + var clone = Clone(); + + clone.isHidden = true; + + return Clone(); + } + + public ModelField Show() + { + if (!isHidden) + { + throw new DomainValidationException($"The field '{name} is already visible."); + } + + var clone = Clone(); + + clone.isHidden = false; + + return Clone(); + } + + public ModelField Disable() + { + if (isDisabled) + { + throw new DomainValidationException($"The field '{name} is already disabled."); + } + + var clone = Clone(); + + clone.isDisabled = true; + + return clone; + } + + public ModelField Enable() + { + if (!isDisabled) + { + throw new DomainValidationException($"The field '{name} is already enabled."); + } + + var clone = Clone(); + + clone.isDisabled = false; + + return clone; + } + + protected abstract ModelField Clone(); + } +} \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs new file mode 100644 index 000000000..e318249d9 --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// ModelFieldFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Core.Schema +{ + public class ModelFieldFactory + { + public virtual ModelField CreateField(Guid id, string type, string fieldName) + { + return new NumberField(id, fieldName); + } + } +} + diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs new file mode 100644 index 000000000..cf46324ee --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs @@ -0,0 +1,177 @@ +// ========================================================================== +// ModelSchema.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using PinkParrot.Infrastructure; + +namespace PinkParrot.Core.Schema +{ + public sealed class ModelSchema + { + private readonly ModelSchemaMetadata metadata; + private readonly ImmutableDictionary fields; + private readonly Dictionary fieldsByName; + + public ModelSchema(ModelSchemaMetadata metadata, ImmutableDictionary fields) + { + Guard.NotNull(fields, nameof(fields)); + Guard.NotNull(metadata, nameof(metadata)); + + this.fields = fields; + + this.metadata = metadata; + + fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + } + + public static ModelSchema Create(string name) + { + if (!name.IsSlug()) + { + throw new DomainValidationException("Cannot create the schema.", $"'{name}' is not a valid slug."); + } + + return new ModelSchema(new ModelSchemaMetadata(name), ImmutableDictionary.Empty); + } + + public IReadOnlyDictionary Fields + { + get { return fields; } + } + + public ModelSchemaMetadata Metadata + { + get { return metadata; } + } + + public ModelSchema Update(ModelSchemaMetadata newMetadata) + { + Guard.NotNull(newMetadata, nameof(newMetadata)); + + return new ModelSchema(newMetadata, fields); + } + + public ModelSchema AddField(Guid id, string type, string fieldName, ModelFieldFactory factory) + { + var field = factory.CreateField(id, type, fieldName); + + return SetField(field); + } + + public ModelSchema SetField(Guid fieldId, PropertiesBag settings) + { + Guard.NotNull(settings, nameof(settings)); + + return UpdateField(fieldId, field => + { + var errors = new List(); + + var newField = field.Configure(settings, errors); + + if (errors.Any()) + { + throw new DomainValidationException($"Cannot update field with id '{fieldId}', becase the settings are invalid.", errors); + } + + return newField; + }); + } + + public ModelSchema DisableField(Guid fieldId) + { + return UpdateField(fieldId, field => field.Disable()); + } + + public ModelSchema EnableField(Guid fieldId) + { + return UpdateField(fieldId, field => field.Enable()); + } + + public ModelSchema HideField(Guid fieldId) + { + return UpdateField(fieldId, field => field.Show()); + } + + public ModelSchema ShowField(Guid fieldId) + { + return UpdateField(fieldId, field => field.Show()); + } + + public ModelSchema SetField(ModelField field) + { + Guard.NotNull(field, nameof(field)); + + if (fields.Values.Any(f => f.Name == field.Name && f.Id != field.Id)) + { + throw new DomainValidationException($"A field with name '{field.Name}' already exists."); + } + + return new ModelSchema(metadata, fields.SetItem(field.Id, field)); + } + + public ModelSchema DeleteField(Guid fieldId) + { + Guard.NotEmpty(fieldId, nameof(fieldId)); + + if (!fields.ContainsKey(fieldId)) + { + throw new DomainValidationException($"A field with id {fieldId} does not exist."); + } + + return new ModelSchema(metadata, fields.Remove(fieldId)); + } + + private ModelSchema UpdateField(Guid fieldId, Func updater) + { + ModelField field; + + if (!fields.TryGetValue(fieldId, out field)) + { + throw new DomainValidationException($"Cannot update field with id '{fieldId}'.", "Field does not exist."); + } + + var newField = updater(field); + + return SetField(newField); + } + + public async Task ValidateAsync(PropertiesBag data) + { + Guard.NotNull(data, nameof(data)); + + var errors = new List(); + + foreach (var kvp in data.Properties) + { + ModelField field; + + if (fieldsByName.TryGetValue(kvp.Key, out field)) + { + var newErrors = new List(); + + await field.ValidateAsync(kvp.Value, newErrors); + + errors.AddRange(newErrors.Select(e => e.Replace("", "'" + field.Name + "'"))); + } + else + { + errors.Add($"'{kvp.Key}' is not a known field"); + } + } + + if (errors.Any()) + { + throw new DomainValidationException("The data is not valid.", errors); + } + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaMetadata.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaMetadata.cs new file mode 100644 index 000000000..63ee493a4 --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaMetadata.cs @@ -0,0 +1,81 @@ +// ========================================================================== +// ModelSchemaMetadata.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using PinkParrot.Infrastructure; + +namespace PinkParrot.Core.Schema +{ + public sealed class ModelSchemaMetadata + { + private string name; + private string displayName; + private string hint; + private string itemTitle; + + public string Name + { + get { return name; } + } + + public string DisplayName + { + get { return displayName; } + } + + public string Hint + { + get { return hint; } + } + + public string ItemTitle + { + get { return itemTitle; } + } + + public ModelSchemaMetadata(string name) + { + Guard.ValidSlug(name, nameof(name)); + + this.name = name; + } + + public ModelSchemaMetadata Configure(string newName, PropertiesBag properties) + { + Guard.NotNull(properties, nameof(properties)); + + var clone = (ModelSchemaMetadata) MemberwiseClone(); + + if (newName != null) + { + if (!newName.IsSlug()) + { + throw new DomainValidationException("Cannot update the schema.", $"'{newName}' is not a valid slug."); + } + + clone.name = newName; + } + + if (properties.Contains("Hint")) + { + clone.hint = properties["Hint"].ToString()?.Trim(); + } + + if (properties.Contains("DisplayName")) + { + clone.displayName = properties["DisplayName"].ToString()?.Trim(); + } + + if (properties.Contains("ItemTitle")) + { + clone.itemTitle = properties["ItemTitle"].ToString()?.Trim(); + } + + return clone; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs new file mode 100644 index 000000000..90d0ad8e1 --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs @@ -0,0 +1,95 @@ +// ========================================================================== +// NumberField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.Tasks; + +namespace PinkParrot.Core.Schema +{ + public sealed class NumberField : ModelField + { + private double? maxValue; + private double? minValue; + + public double? MaxValue + { + get { return maxValue; } + } + + public double? MinValue + { + get { return minValue; } + } + + public NumberField(Guid id, string name) + : base(id, name) + { + } + + protected override void ConfigureCore(PropertiesBag settings, ICollection errors) + { + maxValue = ParseNumber("MaxValue", settings, errors); + minValue = ParseNumber("MinValue", settings, errors); + + if (maxValue.HasValue && minValue.HasValue && minValue.Value > maxValue.Value) + { + errors.Add("MinValue cannot be larger than max value"); + } + } + + private static double? ParseNumber(string key, PropertiesBag settings, ICollection errors) + { + try + { + if (settings.Contains(key)) + { + return settings[key].ToNullableDouble(CultureInfo.InvariantCulture); + } + } + catch (InvalidCastException) + { + errors.Add($"'{key}' is not a valid number"); + } + + return null; + } + + protected override Task ValidateCoreAsync(PropertyValue property, ICollection errors) + { + try + { + var value = property.ToDouble(CultureInfo.InvariantCulture); + + if (MinValue.HasValue && value < MinValue.Value) + { + errors.Add($" must be greater than {MinValue}"); + } + + if (MaxValue.HasValue && value > MaxValue.Value) + { + errors.Add($" must be less than {MaxValue}"); + } + } + catch (InvalidCastException) + { + errors.Add(" is not a valid number"); + } + + return TaskHelper.Done; + } + + protected override ModelField Clone() + { + return (ModelField)MemberwiseClone(); + } + } +} diff --git a/src/pinkparrot_core/PinkParrot.Core/project.json b/src/pinkparrot_core/PinkParrot.Core/project.json new file mode 100644 index 000000000..dc9aa12ab --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/project.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "EventStore.ClientAPI.DotNetCore": "1.0.0", + "NETStandard.Library": "1.6.0", + "NodaTime": "2.0.0-alpha20160729", + "PinkParrot.Infrastructure": "1.0.0-*", + "protobuf-net": "2.1.0" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + + "tooling": { + "defaultNamespace": "PinkParrot.Core" + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/.gitignore b/src/pinkparrot_events/PinkParrot.Events/.gitignore new file mode 100644 index 000000000..0ca27f04e --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/.gitignore @@ -0,0 +1,234 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/src/pinkparrot_events/PinkParrot.Events/PinkParrot.Events.xproj b/src/pinkparrot_events/PinkParrot.Events/PinkParrot.Events.xproj new file mode 100644 index 000000000..eb3e71ee7 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/PinkParrot.Events.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 25f66c64-058a-4d44-bc0c-f12a054f9a91 + PinkParrot.Events + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs new file mode 100644 index 000000000..ca22786d3 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// ModelFieldAdded.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldAdded : IEvent + { + public Guid FieldId { get; set; } + + public string FieldType; + + public string FieldName { get; set; } + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs new file mode 100644 index 000000000..04e5609c6 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ModelFieldDeleted.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldDeleted : IEvent + { + public Guid FieldId; + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs new file mode 100644 index 000000000..cbdaee9a2 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ModelFieldDisabled.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldDisabled : IEvent + { + public Guid FieldId; + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs new file mode 100644 index 000000000..9a8ff9e24 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ModelFieldEnabled.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldEnabled : IEvent + { + public Guid FieldId; + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs new file mode 100644 index 000000000..fde8b7657 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ModelFieldHidden.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldHidden : IEvent + { + public Guid FieldId; + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs new file mode 100644 index 000000000..212fe4114 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ModelFieldShown.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldShown : IEvent + { + public Guid FieldId; + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs new file mode 100644 index 000000000..e993c8068 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// ModelFieldUpdated.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelFieldUpdated : IEvent + { + public Guid FieldId { get; set; } + + public PropertiesBag Settings { get; set; } + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs new file mode 100644 index 000000000..9e9d8f8d7 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// ModelSchemaCreated.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelSchemaCreated : IEvent + { + public string Name; + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs new file mode 100644 index 000000000..1671d9501 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// ModelSchemaDeleted.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelSchemaDeleted : IEvent + { + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs new file mode 100644 index 000000000..fcaaf5225 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// ModelSchemaUpdated.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.CQRS; + +namespace PinkParrot.Events.Schema +{ + public class ModelSchemaUpdated : IEvent + { + public string NewName; + + public PropertiesBag Settings { get; set; } + } +} diff --git a/src/pinkparrot_events/PinkParrot.Events/project.json b/src/pinkparrot_events/PinkParrot.Events/project.json new file mode 100644 index 000000000..e676a1907 --- /dev/null +++ b/src/pinkparrot_events/PinkParrot.Events/project.json @@ -0,0 +1,24 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.6.0", + "NodaTime": "2.0.0-alpha20160729", + "PinkParrot.Infrastructure": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + + "tooling": { + "defaultNamespace": "PinkParrot.Events" + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTest.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTest.cs new file mode 100644 index 000000000..e6f0a62e0 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTest.cs @@ -0,0 +1,169 @@ +// ========================================================================== +// CollectionExtensionsTest.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using Xunit; + +namespace PinkParrot.Infrastructure +{ + public class CollectionExtensionTest + { + private readonly Dictionary valueDictionary = new Dictionary(); + private readonly Dictionary> listDictionary = new Dictionary>(); + + [Fact] + public void GetOrDefault_should_return_value_if_key_exists() + { + valueDictionary[12] = 34; + + Assert.Equal(34, valueDictionary.GetOrDefault(12)); + } + + [Fact] + public void GetOrDefault_should_return_default_and_not_add_it_if_key_not_exists() + { + Assert.Equal(0, valueDictionary.GetOrDefault(12)); + Assert.False(valueDictionary.ContainsKey(12)); + } + + [Fact] + public void GetOrAddDefault_should_return_value_if_key_exists() + { + valueDictionary[12] = 34; + + Assert.Equal(34, valueDictionary.GetOrAddDefault(12)); + } + + [Fact] + public void GetOrAddDefault_should_return_default_and_add_it_if_key_not_exists() + { + Assert.Equal(0, valueDictionary.GetOrAddDefault(12)); + Assert.Equal(0, valueDictionary[12]); + } + + [Fact] + public void GetOrCreate_should_return_value_if_key_exists() + { + valueDictionary[12] = 34; + + Assert.Equal(34, valueDictionary.GetOrCreate(12, x => 34)); + } + + [Fact] + public void GetOrCreate_should_return_default_but_not_add_it_if_key_not_exists() + { + Assert.Equal(24, valueDictionary.GetOrCreate(12, x => 24)); + Assert.False(valueDictionary.ContainsKey(12)); + } + + [Fact] + public void GetOrAdd_should_return_value_if_key_exists() + { + valueDictionary[12] = 34; + + Assert.Equal(34, valueDictionary.GetOrAdd(12, x => 34)); + } + + [Fact] + public void GetOrAdd_should_return_default_and_add_it_if_key_not_exists() + { + Assert.Equal(24, valueDictionary.GetOrAdd(12, x => 24)); + Assert.Equal(24, valueDictionary[12]); + } + + [Fact] + public void GetOrNew_should_return_value_if_key_exists() + { + var list = new List(); + listDictionary[12] = list; + + Assert.Equal(list, listDictionary.GetOrNew(12)); + } + + [Fact] + public void GetOrNew_should_return_default_but_not_add_it_if_key_not_exists() + { + var list = new List(); + + Assert.Equal(list, listDictionary.GetOrNew(12)); + Assert.False(listDictionary.ContainsKey(12)); + } + + [Fact] + public void GetOrAddNew_should_return_value_if_key_exists() + { + var list = new List(); + listDictionary[12] = list; + + Assert.Equal(list, listDictionary.GetOrAddNew(12)); + } + + [Fact] + public void GetOrAddNew_should_return_default_but_not_add_it_if_key_not_exists() + { + var list = new List(); + + Assert.Equal(list, listDictionary.GetOrAddNew(12)); + Assert.Equal(list, listDictionary[12]); + } + + [Fact] + public void SequentialHashCode_should_return_same_hash_codes_for_list_with_same_order() + { + var collection1 = new[] { 3, 5, 6 }; + var collection2 = new[] { 3, 5, 6 }; + + Assert.Equal(collection2.SequentialHashCode(), collection1.SequentialHashCode()); + } + + [Fact] + public void SequentialHashCode_should_return_different_hash_codes_for_list_with_different_items() + { + var collection1 = new[] { 3, 5, 6 }; + var collection2 = new[] { 3, 4, 1 }; + + Assert.NotEqual(collection2.SequentialHashCode(), collection1.SequentialHashCode()); + } + + [Fact] + public void SequentialHashCode_should_return_different_hash_codes_for_list_with_different_order() + { + var collection1 = new[] { 3, 5, 6 }; + var collection2 = new[] { 6, 5, 3 }; + + Assert.NotEqual(collection2.SequentialHashCode(), collection1.SequentialHashCode()); + } + + [Fact] + public void OrderedHashCode_should_return_same_hash_codes_for_list_with_same_order() + { + var collection1 = new[] { 3, 5, 6 }; + var collection2 = new[] { 3, 5, 6 }; + + Assert.Equal(collection2.OrderedHashCode(), collection1.OrderedHashCode()); + } + + [Fact] + public void OrderedHashCode_should_return_different_hash_codes_for_list_with_different_items() + { + var collection1 = new[] { 3, 5, 6 }; + var collection2 = new[] { 3, 4, 1 }; + + Assert.NotEqual(collection2.OrderedHashCode(), collection1.OrderedHashCode()); + } + + [Fact] + public void OrderedHashCode_should_return_same_hash_codes_for_list_with_different_order() + { + var collection1 = new[] { 3, 5, 6 }; + var collection2 = new[] { 6, 5, 3 }; + + Assert.Equal(collection2.OrderedHashCode(), collection1.OrderedHashCode()); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTest.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTest.cs new file mode 100644 index 000000000..17eb65aaa --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/EnumExtensionsTest.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// EnumExtensionsTest.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using Xunit; + +namespace PinkParrot.Infrastructure +{ + public sealed class EnumExtensionsTest + { + [Fact] + public void Should_return_true_if_enum_is_valid() + { + Assert.True(DateTimeKind.Local.IsEnumValue()); + } + + [Fact] + public void Should_return_false_if_enum_is_not_valid() + { + Assert.False(((DateTimeKind)13).IsEnumValue()); + Assert.False(123.IsEnumValue()); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs new file mode 100644 index 000000000..ec4417a14 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs @@ -0,0 +1,128 @@ +// ========================================================================== +// GuardTests.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using Xunit; + +namespace PinkParrot.Infrastructure +{ + public class GuardTest + { + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Should_throw_when_target_is_null_for_empty_string(string invalidString) + { + Assert.Throws(() => Guard.NotNullOrEmpty(invalidString, "parameter")); + } + + [Fact] + public void Should_do_nothing_if_target_string_is_valid() + { + Guard.NotNullOrEmpty("value", "parameter"); + } + + [Fact] + public void Should_do_nothing_if_target_is_not_null() + { + Guard.NotNull("value", "parameter"); + } + + [Fact] + public void Should_do_nothing_if_enum_is_valid() + { + Guard.Enum(DateTimeKind.Local, "Parameter"); + } + + [Fact] + public void Should_throw_if_enum_is_not_valid() + { + Assert.Throws(() => Guard.Enum((DateTimeKind)13, "Parameter")); + } + + [Fact] + public void Should_do_nothing_when_guid_is_not_empty() + { + Guard.NotEmpty(Guid.NewGuid(), "parameter"); + } + + [Fact] + public void Should_throw_when_guid_is_empty() + { + Assert.Throws(() => Guard.NotEmpty(Guid.Empty, "parameter")); + } + + [Fact] + public void Should_throw_when_target_is_null() + { + Assert.Throws(() => Guard.NotNull(null, "parameter")); + } + + [Fact] + public void Should_throw_when_target_is_null_for_null_string() + { + Assert.Throws(() => Guard.NotNullOrEmpty(null, "parameter")); + } + + [Fact] + public void Should_do_nothing_when_target_has_correct_type() + { + Guard.HasType(123, "parameter"); + } + + [Fact] + public void Should_throw_when_target_has_wrong_type() + { + Assert.Throws(() => Guard.HasType("value", "parameter")); + } + + [Fact] + public void Should_throw_when_checking_for_null_and_target_is_null() + { + Assert.Throws(() => Guard.HasType(null, "parameter")); + } + + [Fact] + public void Should_do_nothing_when_target_is_not_default_value() + { + Guard.NotDefault(Guid.NewGuid(), "parameter"); + } + + [Fact] + public void Should_throw_exception_when_value_has_default() + { + Assert.Throws(() => Guard.NotDefault(Guid.Empty, "parameter")); + Assert.Throws(() => Guard.NotDefault(0, "parameter")); + Assert.Throws(() => Guard.NotDefault((string)null, "parameter")); + Assert.Throws(() => Guard.NotDefault(false, "parameter")); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" Not a Slug ")] + [InlineData(" not--a--slug ")] + [InlineData(" not-a-slug ")] + [InlineData("-not-a-slug-")] + [InlineData("not$-a-slug")] + public void Should_throw_exception_for_invalid_slug(string slug) + { + Assert.Throws(() => Guard.ValidSlug(slug, "slug")); + } + + [Theory] + [InlineData("slug")] + [InlineData("slug23")] + [InlineData("other-slug")] + [InlineData("just-another-slug")] + public void Should_do_nothing_for_valid_slug(string slug) + { + Guard.ValidSlug(slug, "parameter"); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj new file mode 100644 index 000000000..d1144a582 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 840c02b1-48f8-4c8a-8862-8a3fdefde8d5 + PinkParrot.Infrastructure + .\obj + .\bin\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs new file mode 100644 index 000000000..9c8b75362 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs @@ -0,0 +1,281 @@ +// ========================================================================== +// PropertiesBagTests.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Globalization; +using System.Linq; +using NodaTime; +using Xunit; + +// ReSharper disable PossibleInvalidOperationException +// ReSharper disable UnusedParameter.Local + +namespace PinkParrot.Infrastructure +{ + public class PropertiesBagTest + { + private readonly CultureInfo c = CultureInfo.InvariantCulture; + private readonly PropertiesBag bag = new PropertiesBag(); + + [Fact] + public void Should_return_false_when_renaming_unknown_property() + { + Assert.False(bag.Rename("OldKey", "NewKey")); + } + + [Fact] + public void Should_throw_when_renaming_to_existing_property() + { + bag.Set("NewKey", 1); + + Assert.Throws(() => bag.Rename("OldKey", "NewKey")); + } + + [Fact] + public void Should_throw_when_renaming_to_same_key() + { + Assert.Throws(() => bag.Rename("SameKey", "SameKey")); + } + + [Fact] + public void Should_provide_property_with_new_name_after_rename() + { + bag.Set("OldKey", 123); + + Assert.True(bag.Rename("OldKey", "NewKey")); + Assert.True(bag.Contains("NewKey")); + + Assert.Equal(1, bag.Count); + Assert.Equal(123, bag["NewKey"].ToInt32(c)); + + Assert.False(bag.Contains("OldKey")); + } + + [Fact] + public void Should_calculate_count_correctly() + { + bag.Set("Key1", 1); + bag.Set("Key2", 1); + + Assert.Equal(2, bag.Count); + Assert.Equal(new[] { "Key1", "Key2" }, bag.PropertyNames.ToArray()); + Assert.Equal(new[] { "Key1", "Key2" }, bag.Properties.Keys.ToArray()); + } + + [Fact] + public void Should_return_correct_value_when_contains_check() + { + Assert.False(bag.Contains("Key")); + + bag.Set("Key", 1); + + Assert.True(bag.Contains("Key")); + Assert.True(bag.Contains("KEY")); + } + + [Fact] + public void Should_returne_false_when_property_to_rename_does_not_exist() + { + Assert.False(bag.Remove("NOTFOUND")); + } + + [Fact] + public void Should_ignore_casing_when_returning() + { + bag.Set("Key", 1); + + Assert.True(bag.Remove("KEY")); + Assert.False(bag.Contains("KEY")); + } + + [Fact] + public void Should_throw_when_setting_value_with_invalid_type() + { + Assert.Throws(() => bag.Set("Key", (byte)1)); + } + + [Fact] + public void Should_convert_string_to_numbers() + { + bag.Set("Key", 123); + + AssertNumber(); + } + + [Fact] + public void Should_convert_int_to_numbers() + { + bag.Set("Key", 123); + + AssertNumber(); + } + + [Fact] + public void Should_convert_long_to_numbers() + { + bag.Set("Key", 123L); + + AssertNumber(); + } + + [Fact] + public void Should_throw_when_casting_from_large_long() + { + bag.Set("Key", long.MaxValue); + + Assert.Throws(() => bag["Key"].ToInt32(c)); + } + + [Fact] + public void Should_convert_float_to_number() + { + bag.Set("Key", 123f); + + AssertNumber(); + } + + [Fact] + public void Should_convert_double_to_number() + { + bag.Set("Key", 123d); + + AssertNumber(); + } + + [Fact] + public void Should_throw_when_casting_from_large_doule() + { + bag.Set("Key", double.MaxValue); + + Assert.Equal(float.PositiveInfinity, bag["Key"].ToSingle(c)); + } + + [Fact] + public void Should_convert_from_instant_value() + { + var time = SystemClock.Instance.GetCurrentInstant(); + + bag.Set("Key", time); + + AssertInstant(time); + } + + [Fact] + public void Should_convert_from_instant_string() + { + var time = SystemClock.Instance.GetCurrentInstant(); + + bag.Set("Key", time.ToString()); + + AssertInstant(time); + } + + [Fact] + public void Should_convert_from_guid_value() + { + var id = new Guid(); + + bag.Set("Key", id); + + AssertGuid(id); + } + + [Fact] + public void Should_convert_from_guid_string() + { + var id = new Guid(); + + bag.Set("Key", id.ToString()); + + AssertGuid(id); + } + + [Fact] + public void Should_convert_from_boolean_value() + { + bag.Set("Key", true); + + AssertBoolean(); + } + + [Fact] + public void Should_convert_from_boolean_string() + { + bag.Set("Key", "true"); + + AssertBoolean(); + } + + [Fact] + public void Should_convert_boolean_from_number() + { + bag.Set("Key", 1); + + AssertBoolean(); + } + + [Fact] + public void Should_throw_when_converting_instant_to_number() + { + bag.Set("Key", SystemClock.Instance.GetCurrentInstant()); + + Assert.Throws(() => bag["Key"].ToGuid(CultureInfo.InvariantCulture)); + } + + [Fact] + public void Should_return_default_when_property_value_is_null() + { + bag.Set("Key", null); + + Assert.Equal(null, bag["Key"].ToString()); + + Assert.Equal(0f, bag["Key"].ToSingle(CultureInfo.CurrentCulture)); + Assert.Equal(0d, bag["Key"].ToDouble(CultureInfo.CurrentCulture)); + Assert.Equal(0L, bag["Key"].ToInt64(CultureInfo.CurrentCulture)); + Assert.Equal(0, bag["Key"].ToInt32(CultureInfo.CurrentCulture)); + + Assert.Equal(false, bag["Key"].ToBoolean(CultureInfo.CurrentCulture)); + + Assert.Equal(new Guid(), bag["Key"].ToGuid(CultureInfo.CurrentCulture)); + + Assert.Equal(new Instant(), bag["Key"].ToInstant(CultureInfo.CurrentCulture)); + } + + private void AssertBoolean() + { + Assert.True(bag["Key"].ToBoolean(c)); + Assert.True(bag["Key"].ToNullableBoolean(c)); + } + + private void AssertInstant(Instant expected) + { + Assert.Equal(expected.ToUnixTimeSeconds(), bag["Key"].ToInstant(c).ToUnixTimeSeconds()); + Assert.Equal(expected.ToUnixTimeSeconds(), bag["Key"].ToNullableInstant(c).Value.ToUnixTimeSeconds()); + } + + private void AssertGuid(Guid expected) + { + Assert.Equal(expected, bag["Key"].ToGuid(c)); + Assert.Equal(expected, bag["Key"].ToNullableGuid(c)); + } + + private void AssertNumber() + { + Assert.Equal(123, bag["Key"].ToInt32(c)); + Assert.Equal(123, bag["Key"].ToNullableInt32(c)); + Assert.Equal(123L, bag["Key"].ToInt64(c)); + Assert.Equal(123L, bag["Key"].ToNullableInt64(c)); + Assert.Equal(123f, bag["Key"].ToSingle(c)); + Assert.Equal(123f, bag["Key"].ToNullableSingle(c)); + Assert.Equal(123d, bag["Key"].ToDouble(c)); + Assert.Equal(123d, bag["Key"].ToNullableDouble(c)); + + Assert.True(bag["Key"].ToBoolean(c)); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/project.json b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/project.json new file mode 100644 index 000000000..baaaf4223 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/project.json @@ -0,0 +1,29 @@ +{ + "version": "1.0.0-*", + "testRunner": "xunit", + "dependencies": { + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "PinkParrot.Infrastructure": "1.0.0-*", + "xunit": "2.2.0-beta2-build3300" + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + "buildOptions": { + "copyToOutput": { + "include": [ + "xunit.runner.json" + ] + } + }, + "tooling": { + "defaultNamespace": "PinkParrot.Core.Tests" + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/xunit.runner.json b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/xunit.runner.json new file mode 100644 index 000000000..6b3f1f87d --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "diagnosticMessages": false, + "methodDisplay": "classAndMethod", + "parallelizeTestCollections": true +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs new file mode 100644 index 000000000..7df63ae08 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// CommonHeaders.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Infrastructure.CQRS +{ + public sealed class CommonHeaders + { + public const string AggregateId = "AggregateId"; + public const string CommitId = "CommitId"; + public const string Timestamp = "Timestamp"; + public const string EventId = "EventId"; + public const string EventType = "EventType"; + public const string EventNumber = "EventNumber"; + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs new file mode 100644 index 000000000..ab4a02804 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs @@ -0,0 +1,105 @@ +// ========================================================================== +// DomainObject.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; + +namespace PinkParrot.Infrastructure.CQRS +{ + public abstract class DomainObject : IAggregate, IEquatable + { + private readonly List> uncomittedEvents = new List>(); + private readonly Guid id; + private int version; + + public int Version + { + get { return version; } + } + + public Guid Id + { + get { return id; } + } + + protected DomainObject(Guid id, int version) + { + Guard.NotEmpty(id, nameof(id)); + Guard.GreaterEquals(version, 0, nameof(version)); + + this.id = id; + + this.version = version; + } + + protected void RaiseEvent(Envelope envelope, bool disableApply = false) where TEvent : class, IEvent + { + Guard.NotNull(envelope, nameof(envelope)); + + uncomittedEvents.Add(envelope.To()); + + if (!disableApply) + { + ApplyEvent(envelope.Payload); + } + } + + protected void RaiseEvent(IEvent @event, bool disableApply = false) + { + Guard.NotNull(@event, nameof(@event)); + + uncomittedEvents.Add(EnvelopeFactory.ForEvent(@event, id)); + + if (!disableApply) + { + ApplyEvent(@event); + } + } + + protected void Apply(object @event) + { + } + + private void ApplyEvent(dynamic @event) + { + Apply(@event); + version++; + } + + void IAggregate.ApplyEvent(IEvent @event) + { + Apply(@event as dynamic); + version++; + } + + void IAggregate.ClearUncommittedEvents() + { + uncomittedEvents.Clear(); + } + + ICollection> IAggregate.GetUncomittedEvents() + { + return uncomittedEvents; + } + + public override int GetHashCode() + { + return id.GetHashCode(); + } + + public override bool Equals(object obj) + { + return Equals(obj as IAggregate); + } + + public bool Equals(IAggregate other) + { + return other != null && other.Id.Equals(id); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs new file mode 100644 index 000000000..d79df3091 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// Envelope.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Infrastructure.CQRS +{ + public class Envelope where TPayload : class + { + private readonly EnvelopeHeaders headers; + private readonly TPayload payload; + + public EnvelopeHeaders Headers + { + get + { + return headers; + } + } + + public TPayload Payload + { + get + { + return payload; + } + } + + public Envelope(TPayload payload) + { + Guard.NotNull(payload, nameof(payload)); + + this.payload = payload; + + headers = new EnvelopeHeaders(); + } + + public Envelope(TPayload payload, EnvelopeHeaders headers) + { + Guard.NotNull(payload, nameof(payload)); + Guard.NotNull(headers, nameof(headers)); + + this.payload = payload; + this.headers = headers; + } + + public Envelope To() where TOther : class + { + return new Envelope(payload as TOther, headers.Clone()); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs new file mode 100644 index 000000000..5d384486b --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// EnvelopeExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Globalization; +using NodaTime; + +namespace PinkParrot.Infrastructure.CQRS +{ + public static class EnvelopeExtensions + { + public static int EventNumber(this Envelope envelope) where T : class + { + return envelope.Headers[CommonHeaders.EventNumber].ToInt32(CultureInfo.InvariantCulture); + } + + public static Envelope SetEventNumber(this Envelope envelope, int value) where T : class + { + envelope.Headers.Set(CommonHeaders.EventNumber, value); + + return envelope; + } + + public static Guid CommitId(this Envelope envelope) where T : class + { + return envelope.Headers[CommonHeaders.CommitId].ToGuid(CultureInfo.InvariantCulture); + } + + public static Envelope SetCommitId(this Envelope envelope, Guid value) where T : class + { + envelope.Headers.Set(CommonHeaders.CommitId, value); + + return envelope; + } + + public static Guid AggregateId(this Envelope envelope) where T : class + { + return envelope.Headers[CommonHeaders.AggregateId].ToGuid(CultureInfo.InvariantCulture); + } + + public static Envelope SetAggregateId(this Envelope envelope, Guid value) where T : class + { + envelope.Headers.Set(CommonHeaders.AggregateId, value); + + return envelope; + } + + public static Guid EventId(this Envelope envelope) where T : class + { + return envelope.Headers[CommonHeaders.EventId].ToGuid(CultureInfo.InvariantCulture); + } + + public static Envelope SetEventId(this Envelope envelope, Guid value) where T : class + { + envelope.Headers.Set(CommonHeaders.EventId, value); + + return envelope; + } + + public static Instant Timestamp(this Envelope envelope) where T : class + { + return envelope.Headers[CommonHeaders.Timestamp].ToInstant(CultureInfo.InvariantCulture); + } + + public static Envelope SetTimestamp(this Envelope envelope, Instant value) where T : class + { + envelope.Headers.Set(CommonHeaders.Timestamp, value); + + return envelope; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs new file mode 100644 index 000000000..fcfa1d203 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// EnvelopeFactory.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using NodaTime; + +namespace PinkParrot.Infrastructure.CQRS +{ + public static class EnvelopeFactory + { + public static Envelope ForEvent(IEvent @event, Guid aggregateId) + { + return new Envelope(@event) + .SetAggregateId(aggregateId) + .SetEventId(aggregateId) + .SetTimestamp(SystemClock.Instance.GetCurrentInstant()); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs new file mode 100644 index 000000000..fb0092932 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// EnvelopeHeaders.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Infrastructure.CQRS +{ + public sealed class EnvelopeHeaders : PropertiesBag + { + public EnvelopeHeaders Clone() + { + var clone = new EnvelopeHeaders(); + + foreach (var property in Properties) + { + clone.Set(property.Key, property.Value.RawValue); + } + + return clone; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs new file mode 100644 index 000000000..f640513c4 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IAggregate.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// IAggregate.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; + +namespace PinkParrot.Infrastructure.CQRS +{ + public interface IAggregate + { + Guid Id { get; } + + int Version { get; } + + void ApplyEvent(IEvent @event); + + void ClearUncommittedEvents(); + + ICollection> GetUncomittedEvents(); + } +} + + diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IEvent.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IEvent.cs new file mode 100644 index 000000000..6ee3da7ea --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/IEvent.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// IEvent.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Infrastructure.CQRS +{ + public interface IEvent + { + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs new file mode 100644 index 000000000..dc47c0a3d --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs @@ -0,0 +1,110 @@ +// ========================================================================== +// CollectionExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; + +// ReSharper disable InvertIf +// ReSharper disable LoopCanBeConvertedToQuery + +namespace PinkParrot.Infrastructure +{ + public static class CollectionExtensions + { + public static int SequentialHashCode(this IEnumerable collection) + { + return collection.SequentialHashCode(EqualityComparer.Default); + } + + public static int SequentialHashCode(this IEnumerable collection, IEqualityComparer comparer) + { + var hashCode = 17; + + foreach (var item in collection) + { + if (item != null) + { + hashCode = hashCode * 23 + item.GetHashCode(); + } + } + + return hashCode; + } + + public static int OrderedHashCode(this IEnumerable collection) + { + return collection.OrderedHashCode(EqualityComparer.Default); + } + + public static int OrderedHashCode(this IEnumerable collection, IEqualityComparer comparer) + { + var hashCodes = collection.Where(x => x != null).Select(x => x.GetHashCode()).OrderBy(x => x).ToArray(); + + var hashCode = 17; + + foreach (var code in hashCodes) + { + hashCode = hashCode * 23 + code; + } + + return hashCode; + } + + public static bool EqualsDictionary(this IReadOnlyDictionary dictionary, IDictionary other) + { + return Equals(dictionary, other) || (other != null && dictionary.Count == other.Count && !dictionary.Except(other).Any()); + } + + public static TValue GetOrDefault(this IReadOnlyDictionary dictionary, TKey key) + { + return dictionary.GetOrCreate(key, _ => default(TValue)); + } + + public static TValue GetOrAddDefault(this IDictionary dictionary, TKey key) + { + return dictionary.GetOrAdd(key, _ => default(TValue)); + } + + public static TValue GetOrNew(this IReadOnlyDictionary dictionary, TKey key) where TValue : class, new() + { + return dictionary.GetOrCreate(key, _ => new TValue()); + } + + public static TValue GetOrAddNew(this IDictionary dictionary, TKey key) where TValue : class, new() + { + return dictionary.GetOrAdd(key, _ => new TValue()); + } + + public static TValue GetOrCreate(this IReadOnlyDictionary dictionary, TKey key, Func creator) + { + TValue result; + + if (!dictionary.TryGetValue(key, out result)) + { + result = creator(key); + } + + return result; + } + + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func creator) + { + TValue result; + + if (!dictionary.TryGetValue(key, out result)) + { + result = creator(key); + + dictionary.Add(key, result); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainValidationException.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainValidationException.cs new file mode 100644 index 000000000..5b3ea411a --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainValidationException.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// DomainValidationException.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PinkParrot.Infrastructure +{ + public class DomainValidationException : Exception + { + private readonly IReadOnlyList errors; + + public IReadOnlyList Errors + { + get { return errors; } + } + + public DomainValidationException(string message, params string[] errors) + : base(message) + { + this.errors = errors != null ? errors.ToList() : new List(); + } + + public DomainValidationException(string message, IReadOnlyList errors) + : base(message) + { + this.errors = errors ?? new List(); + } + + public DomainValidationException(string message, Exception inner, params string[] errors) + : base(message, inner) + { + this.errors = errors != null ? errors.ToList() : new List(); + } + + public DomainValidationException(string message, Exception inner, IReadOnlyList errors) + : base(message, inner) + { + this.errors = errors ?? new List(); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/EnumExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/EnumExtensions.cs new file mode 100644 index 000000000..4b01cb588 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/EnumExtensions.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// EnumExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure +{ + public static class EnumExtensions + { + public static bool IsEnumValue(this TEnum value) where TEnum : struct + { + try + { + return Enum.IsDefined(typeof(TEnum), value); + } + catch + { + return false; + } + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Extensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Extensions.cs new file mode 100644 index 000000000..1d2b453cd --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Extensions.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Extensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace PinkParrot.Infrastructure +{ + public static class Extensions + { + private static readonly Regex SlugRegex = new Regex("^[a-z0-9]+(\\-[a-z0-9]+)*$", RegexOptions.Compiled); + + public static bool IsSlug(this string value) + { + return value != null && SlugRegex.IsMatch(value); + } + + public static bool IsBetween(this TValue value, TValue low, TValue high) where TValue : IComparable + { + return Comparer.Default.Compare(low, value) <= 0 && Comparer.Default.Compare(high, value) >= 0; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs new file mode 100644 index 000000000..f999e5560 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs @@ -0,0 +1,240 @@ +// ========================================================================== +// Guard.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +// ReSharper disable InvertIf + +namespace PinkParrot.Infrastructure +{ + public static class Guard + { + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidNumber(float target, string parameterName) + { + if (float.IsNaN(target) || float.IsPositiveInfinity(target) || float.IsNegativeInfinity(target)) + { + throw new ArgumentException("Value must be a valid number.", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidNumber(double target, string parameterName) + { + if (double.IsNaN(target) || double.IsPositiveInfinity(target) || double.IsNegativeInfinity(target)) + { + throw new ArgumentException("Value must be a valid number.", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidSlug(string target, string parameterName) + { + NotNullOrEmpty(target, parameterName); + + if (!target.IsSlug()) + { + throw new ArgumentException("Target is not a valid slug.", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void HasType(object target, string parameterName) + { + NotNull(target, "parameterName"); + + if (target.GetType() != typeof(T)) + { + throw new ArgumentException("The parameter must be of type " + typeof(T), parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Between(TValue target, TValue lower, TValue upper, string parameterName) where TValue : IComparable + { + if (!target.IsBetween(lower, upper)) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be between {0} and {1}", lower, upper); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Enum(TEnum target, string parameterName) where TEnum : struct + { + if (!target.IsEnumValue()) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be a valid enum type {0}", typeof(TEnum)); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(TValue target, TValue lower, string parameterName) where TValue : IComparable + { + if (target.CompareTo(lower) <= 0) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", lower); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterEquals(TValue target, TValue lower, string parameterName) where TValue : IComparable + { + if (target.CompareTo(lower) < 0) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", lower); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(TValue target, TValue upper, string parameterName) where TValue : IComparable + { + if (target.CompareTo(upper) >= 0) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", upper); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessEquals(TValue target, TValue upper, string parameterName) where TValue : IComparable + { + if (target.CompareTo(upper) > 0) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", upper); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEmpty(ICollection enumerable, string parameterName) + { + if (enumerable == null) + { + throw new ArgumentNullException(nameof(enumerable)); + } + + if (enumerable.Count == 0) + { + throw new ArgumentException("Collection does not contain an item", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEmpty(Guid target, string parameterName) + { + if (target == Guid.Empty) + { + throw new ArgumentException("Value cannot be empty.", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNull(object target, string parameterName) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotDefault(T target, string parameterName) + { + if (Equals(target, default(T))) + { + throw new ArgumentException("Value cannot be an the default value", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNullOrEmpty(string target, string parameterName, bool allowWhitespacesAtStartOrEnd = true) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + + if (string.IsNullOrWhiteSpace(target)) + { + throw new ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName); + } + + if (!allowWhitespacesAtStartOrEnd && target.Trim() != target) + { + throw new ArgumentException("String cannot start or end with whitespaces", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidFileName(string target, string parameterName) + { + NotNullOrEmpty(target, parameterName); + + if (target.Intersect(Path.GetInvalidFileNameChars()).Any()) + { + throw new ArgumentException("Value contains an invalid character.", parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IsType(object target, string parameterName) + { + if (target != null && target.GetType() != typeof(T)) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be of type {0}", typeof(T)); + + throw new ArgumentException(message, parameterName); + } + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IsType(object target, Type expectedType, string parameterName) + { + if (target != null && expectedType != null && target.GetType() != expectedType) + { + var message = string.Format(CultureInfo.CurrentCulture, "Value must be of type {0}", expectedType); + + throw new ArgumentException(message, parameterName); + } + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PinkParrot.Infrastructure.xproj b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PinkParrot.Infrastructure.xproj new file mode 100644 index 000000000..c7fc160b4 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PinkParrot.Infrastructure.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + bd1c30a8-8ffa-4a92-a9bd-b67b1cddd84c + PinkParrot.Infrastructure + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs new file mode 100644 index 000000000..4d773e167 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs @@ -0,0 +1,94 @@ +// ========================================================================== +// PropertiesBag.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; + +namespace PinkParrot.Infrastructure +{ + public class PropertiesBag + { + private readonly Dictionary internalDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public int Count + { + get { return internalDictionary.Count; } + } + + public IReadOnlyDictionary Properties + { + get { return internalDictionary; } + } + + public IEnumerable PropertyNames + { + get { return internalDictionary.Keys; } + } + + public PropertyValue this[string propertyName] + { + get + { + Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); + + return internalDictionary[propertyName]; + } + } + + public bool Contains(string propertyName) + { + Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); + + return internalDictionary.ContainsKey(propertyName); + } + + public PropertiesBag Set(string propertyName, object value) + { + Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); + + internalDictionary[propertyName] = new PropertyValue(value); + + return this; + } + + public bool Remove(string propertyName) + { + Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); + + return internalDictionary.Remove(propertyName); + } + + public bool Rename(string oldPropertyName, string newPropertyName) + { + Guard.NotNullOrEmpty(oldPropertyName, nameof(oldPropertyName)); + Guard.NotNullOrEmpty(newPropertyName, nameof(newPropertyName)); + + if (internalDictionary.ContainsKey(newPropertyName)) + { + throw new ArgumentException($"An property with the key '{newPropertyName}' already exists.", newPropertyName); + } + + if (string.Equals(oldPropertyName, newPropertyName, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException($"The property names '{newPropertyName}' are equal.", newPropertyName); + } + + PropertyValue property; + + if (!internalDictionary.TryGetValue(oldPropertyName, out property)) + { + return false; + } + + internalDictionary[newPropertyName] = property; + internalDictionary.Remove(oldPropertyName); + + return true; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs new file mode 100644 index 000000000..404ad969e --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs @@ -0,0 +1,211 @@ +// ========================================================================== +// PropertyValue.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Globalization; +using NodaTime; +using NodaTime.Text; + +namespace PinkParrot.Infrastructure +{ + public sealed class PropertyValue + { + private readonly object rawValue; + + private static readonly HashSet AllowedTypes = new HashSet + { + typeof(string), + typeof(bool), + typeof(bool?), + typeof(float), + typeof(float?), + typeof(double), + typeof(double?), + typeof(int), + typeof(int?), + typeof(long), + typeof(long?), + typeof(Instant), + typeof(Instant?), + typeof(Guid), + typeof(Guid?) + }; + + public object RawValue + { + get { return rawValue; } + } + + internal PropertyValue(object rawValue) + { + if (rawValue != null && !AllowedTypes.Contains(rawValue.GetType())) + { + throw new ArgumentException("The type is not supported.", nameof(rawValue)); + } + + this.rawValue = rawValue; + } + + public override string ToString() + { + return rawValue?.ToString(); + } + + public bool ToBoolean(CultureInfo culture) + { + return ToOrParseValue(culture, bool.Parse); + } + + public bool? ToNullableBoolean(CultureInfo culture) + { + return ToNullableOrParseValue(culture, bool.Parse); + } + + public float ToSingle(CultureInfo culture) + { + return ToOrParseValue(culture, x => float.Parse(x, culture)); + } + + public float? ToNullableSingle(CultureInfo culture) + { + return ToNullableOrParseValue(culture, x => float.Parse(x, culture)); + } + + public double ToDouble(CultureInfo culture) + { + return ToOrParseValue(culture, x => double.Parse(x, culture)); + } + + public double? ToNullableDouble(CultureInfo culture) + { + return ToNullableOrParseValue(culture, x => double.Parse(x, culture)); + } + + public int ToInt32(CultureInfo culture) + { + return ToOrParseValue(culture, x => int.Parse(x, culture)); + } + + public int? ToNullableInt32(CultureInfo culture) + { + return ToNullableOrParseValue(culture, x => int.Parse(x, culture)); + } + + public long ToInt64(CultureInfo culture) + { + return ToOrParseValue(culture, x => long.Parse(x, culture)); + } + + public long? ToNullableInt64(CultureInfo culture) + { + return ToNullableOrParseValue(culture, x => long.Parse(x, culture)); + } + + public Instant ToInstant(CultureInfo culture) + { + return ToOrParseValue(culture, x => InstantPattern.GeneralPattern.Parse(x).Value); + } + + public Instant? ToNullableInstant(CultureInfo culture) + { + return ToNullableOrParseValue(culture, x => InstantPattern.GeneralPattern.Parse(x).Value); + } + + public Guid ToGuid(CultureInfo culture) + { + return ToOrParseValue(culture, Guid.Parse); + } + + public Guid? ToNullableGuid(CultureInfo culture) + { + return ToNullableOrParseValue(culture, Guid.Parse); + } + + private T? ToNullableOrParseValue(IFormatProvider culture, Func parser) where T : struct + { + T result; + + return TryParse(culture, parser, out result) ? result : (T?)null; + } + + private T ToOrParseValue(IFormatProvider culture, Func parser) + { + T result; + + return TryParse(culture, parser, out result) ? result : default(T); + } + + private bool TryParse(IFormatProvider culture, Func parser, out T result) + { + var value = rawValue; + + if (value != null) + { + var valueType = value.GetType(); + + if (valueType == typeof(T)) + { + result = (T)value; + } + else if (valueType == typeof(string)) + { + result = Parse(parser, valueType, value); + } + else + { + result = Convert(culture, value, valueType); + } + + return true; + } + + result = default(T); + + return false; + } + + private static T Convert(IFormatProvider culture, object value, Type valueType) + { + var requestedType = typeof(T); + + try + { + return (T)System.Convert.ChangeType(value, requestedType, culture); + } + catch (OverflowException) + { + string message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}' because it is either too small or large."; + + throw new InvalidCastException(message); + } + catch (InvalidCastException) + { + string message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}'."; + + throw new InvalidCastException(message); + } + } + + private static T Parse(Func parser, Type valueType, object value) + { + var requestedType = typeof(T); + + try + { + return parser(value.ToString()); + } + catch (Exception e) + { + string message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}'."; + + throw new InvalidCastException(message, e); + } + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Tasks/TaskExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Tasks/TaskExtensions.cs new file mode 100644 index 000000000..ad2eee47c --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Tasks/TaskExtensions.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// TaskExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.Tasks +{ + public static class TaskExtensions + { + public static void Forget(this Task task) + { + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Tasks/TaskHelper.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Tasks/TaskHelper.cs new file mode 100644 index 000000000..f7848ce9b --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Tasks/TaskHelper.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// TaskHelper.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace PinkParrot.Infrastructure.Tasks +{ + public static class TaskHelper + { + public static readonly Task Done = CreateDoneTask(); + + private static Task CreateDoneTask() + { + var result = new TaskCompletionSource(); + + result.SetResult(null); + + return result.Task; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json new file mode 100644 index 000000000..51889eb58 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "NodaTime": "2.0.0-alpha20160729", + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + "tooling": { + "defaultNamespace": "PinkParrot.Infrastructure" + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/.gitignore b/src/pinkparrot_write/PinkParrot.Write/.gitignore new file mode 100644 index 000000000..0ca27f04e --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/.gitignore @@ -0,0 +1,234 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/src/pinkparrot_write/PinkParrot.Write/PinkParrot.Write.xproj b/src/pinkparrot_write/PinkParrot.Write/PinkParrot.Write.xproj new file mode 100644 index 000000000..df8113b99 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/PinkParrot.Write.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + a85201c6-6af8-4b63-8365-08f741050438 + PinkParrot.Write + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs new file mode 100644 index 000000000..1a334abd6 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// AddModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class AddModelField + { + public Guid AggregateId; + + public string FieldType; + + public string FieldName; + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs new file mode 100644 index 000000000..cf7109f6d --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// CreateModelSchema.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class CreateModelSchema + { + public Guid AggregateId; + + public string Name; + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs new file mode 100644 index 000000000..434cd385f --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// DeleteModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class DeleteModelField + { + public Guid AggregateId; + + public Guid FieldId; + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs new file mode 100644 index 000000000..dd737f813 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// DeleteModelSchema.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class DeleteModelSchema + { + public Guid AggregateId; + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs new file mode 100644 index 000000000..3a115a3b9 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// DisableModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class DisableModelField + { + public Guid AggregateId; + + public Guid FieldId; + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs new file mode 100644 index 000000000..5d99b1b82 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// EnableModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class EnableModelField + { + public Guid AggregateId; + + public Guid FieldId; + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs new file mode 100644 index 000000000..dc4c9ffe8 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// HideModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class HideModelField + { + public Guid AggregateId; + + public Guid FieldId; + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs new file mode 100644 index 000000000..f65f8dd9e --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// ShowModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Write.Schema.Commands +{ + public class ShowModelField + { + public Guid AggregateId; + + public Guid FieldId; + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs new file mode 100644 index 000000000..68c604dfe --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// UpdateModelField.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure; + +namespace PinkParrot.Write.Schema.Commands +{ + public class UpdateModelField + { + public Guid AggregateId; + + public Guid FieldId; + + public PropertiesBag Settings; + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs new file mode 100644 index 000000000..75b636e49 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// UpdateModelSchema.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure; + +namespace PinkParrot.Write.Schema.Commands +{ + public class UpdateModelSchema + { + public Guid AggregateId; + + public string NewName; + + public PropertiesBag Settings { get; set; } + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs new file mode 100644 index 000000000..d7a3171e6 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs @@ -0,0 +1,196 @@ +// ========================================================================== +// ModelSchemaDomainObject.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Core.Schema; +using PinkParrot.Events.Schema; +using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Write.Schema.Commands; + +namespace PinkParrot.Write.Schema +{ + public class ModelSchemaDomainObject : DomainObject + { + private readonly ModelFieldFactory fieldFactory; + private bool isDeleted; + private ModelSchema schema; + + public ModelSchemaDomainObject(Guid id, int version, ModelFieldFactory fieldFactory) + : base(id, version) + { + this.fieldFactory = fieldFactory; + } + + public ModelSchema Schema + { + get { return schema; } + } + + public bool IsDeleted + { + get { return isDeleted; } + } + + protected void Apply(ModelSchemaCreated @event) + { + schema = ModelSchema.Create(@event.Name); + } + + protected void Apply(ModelFieldAdded @event) + { + schema = schema.AddField(@event.FieldId, @event.FieldType, @event.FieldName, fieldFactory); + } + + protected void Apply(ModelFieldUpdated @event) + { + schema = schema.SetField(@event.FieldId, @event.Settings); + } + + public void Apply(ModelFieldHidden @event) + { + schema = schema.HideField(@event.FieldId); + } + + public void Apply(ModelFieldShown @event) + { + schema = schema.ShowField(@event.FieldId); + } + + public void Apply(ModelFieldDisabled @event) + { + schema = schema.DisableField(@event.FieldId); + } + + public void Apply(ModelFieldEnabled @event) + { + schema = schema.EnableField(@event.FieldId); + } + + protected void Apply(ModelSchemaUpdated @event) + { + schema = schema.Update(schema.Metadata.Configure(@event.NewName, @event.Settings)); + } + + protected void Apply(ModelFieldDeleted @event) + { + schema = schema.DeleteField(@event.FieldId); + } + + protected void Apply(ModelSchemaDeleted @event) + { + isDeleted = false; + } + + public void Create(CreateModelSchema command) + { + VerifyNotCreated(); + + schema = ModelSchema.Create(command.Name); + + RaiseEvent(new ModelSchemaCreated {Name = command.Name}, true); + } + + public void AddField(Guid id, AddModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.AddField(id, command.FieldType, command.FieldName, fieldFactory); + + RaiseEvent( + new ModelFieldAdded {FieldId = id, FieldType = command.FieldType, FieldName = command.FieldName}, true); + } + + public void Update(UpdateModelSchema command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.Update(schema.Metadata.Configure(command.NewName, command.Settings)); + + RaiseEvent(new ModelSchemaUpdated {NewName = command.NewName, Settings = command.Settings}, true); + } + + public void UpdateField(UpdateModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.SetField(command.FieldId, command.Settings); + + RaiseEvent(new ModelFieldUpdated {FieldId = command.FieldId, Settings = command.Settings}, true); + } + + public void HideField(HideModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.HideField(command.FieldId); + + RaiseEvent(new ModelFieldHidden {FieldId = command.FieldId}, true); + } + + public void ShowField(HideModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.ShowField(command.FieldId); + + RaiseEvent(new ModelFieldShown {FieldId = command.FieldId}, true); + } + + public void DisableField(DisableModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.DisableField(command.FieldId); + + RaiseEvent(new ModelFieldDisabled {FieldId = command.FieldId}, true); + } + + public void EnableField(EnableModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.EnableField(command.FieldId); + + RaiseEvent(new ModelFieldEnabled {FieldId = command.FieldId}, true); + } + + public void Delete(DeleteModelSchema command) + { + VerifyCreatedAndNotDeleted(); + + isDeleted = true; + + RaiseEvent(new ModelSchemaDeleted(), true); + } + + public void DeleteField(DeleteModelField command) + { + VerifyCreatedAndNotDeleted(); + + schema = schema.DeleteField(command.FieldId); + + RaiseEvent(new ModelFieldDeleted {FieldId = command.FieldId}, true); + } + + private void VerifyNotCreated() + { + if (schema != null) + { + throw new InvalidOperationException("Schema has already been created."); + } + } + + private void VerifyCreatedAndNotDeleted() + { + if (isDeleted || schema == null) + { + throw new InvalidOperationException("Schema has already been deleted or not created yet."); + } + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/project.json b/src/pinkparrot_write/PinkParrot.Write/project.json new file mode 100644 index 000000000..86f18c4ae --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/project.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.6.0", + "NodaTime": "2.0.0-alpha20160729", + "PinkParrot.Core": "1.0.0-*", + "PinkParrot.Events": "1.0.0-*", + "PinkParrot.Infrastructure": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + } + } + } + }, + + "tooling": { + "defaultNamespace": "PinkParrot.Write" + } +}