diff --git a/.editorconfig b/.editorconfig index a1501f7..9eac22f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,187 +1,189 @@ -# NOTE: Requires **VS2019 16.3** or later - -# Squidex Rules -# Description: Squidex Rules - -[*.json] -indent_style = space -indent_size = 4 - -[*.cs] -charset = utf-8-bom -insert_final_newline = true -indent_style = space -indent_size = 4 - -csharp_style_namespace_declarations = file_scoped - -# FAILING ANALYZERS -dotnet_diagnostic.RECS0002.severity = none -dotnet_diagnostic.RECS0117.severity = none -dotnet_diagnostic.SA0001.severity = none -dotnet_diagnostic.SA1649.severity = none - -# IDE0305: Simplify collection initialization -dotnet_diagnostic.IDE0305.severity = none - -# CA1707: Identifiers should not contain underscores -dotnet_diagnostic.CA1707.severity = none - -# CA2016: Forward the 'CancellationToken' parameter to methods -dotnet_diagnostic.CA2016.severity = none - -# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -dotnet_diagnostic.CS8618.severity = none - -# IDE0063: Use simple 'using' statement -dotnet_diagnostic.IDE0063.severity = none - -# IDE0066: Convert switch statement to expression -dotnet_diagnostic.IDE0066.severity = none - -# IDE0090: Use 'new(...)' -dotnet_diagnostic.IDE0090.severity = none - -# MA0002: IEqualityComparer or IComparer is missing -dotnet_diagnostic.MA0002.severity = none - -# MA0003: Add argument name to improve readability -dotnet_diagnostic.MA0003.severity = none - -# MA0004: Use Task.ConfigureAwait(false) -dotnet_diagnostic.MA0004.severity = none - -# MA0006: Use String.Equals instead of equality operator -dotnet_diagnostic.MA0006.severity = none - -# MA0007: Add a comma after the last value -dotnet_diagnostic.MA0007.severity = warning - -# MA0008: Add StructLayoutAttribute -dotnet_diagnostic.MA0008.severity = none - -# MA0009: Add regex evaluation timeout -dotnet_diagnostic.MA0009.severity = none - -# MA0016: Prefer return collection abstraction instead of implementation -dotnet_diagnostic.MA0016.severity = none - -# MA0018: Do not declare static members on generic types -dotnet_diagnostic.MA0018.severity = none - -# MA0025: Implement the functionality instead of throwing NotImplementedException -dotnet_diagnostic.MA0025.severity = none - -# MA0028: Optimize StringBuilder usage -dotnet_diagnostic.MA0028.severity = none - -# MA0029: Combine LINQ methods -dotnet_diagnostic.MA0029.severity = none - -# MA0031: Optimize Enumerable.Count() usage -dotnet_diagnostic.MA0031.severity = none - -# MA0036: Make class static -dotnet_diagnostic.MA0036.severity = none - -# MA0038: Make method static -dotnet_diagnostic.MA0038.severity = none - -# MA0039: Do not write your own certificate validation method -dotnet_diagnostic.MA0039.severity = none - -# MA0049: Type name should not match containing namespace -dotnet_diagnostic.MA0049.severity = none - -# MA0051: Method is too long -dotnet_diagnostic.MA0051.severity = none - -# MA0069: Non-constant static fields should not be visible -dotnet_diagnostic.MA0069.severity = none - -# MA0071: Avoid using redundant else -dotnet_diagnostic.MA0071.severity = none - -# MA0076: Do not use implicit culture-sensitive ToString in interpolated strings -dotnet_diagnostic.MA0076.severity = none - -# MA0097: A class that implements IComparable or IComparable should override comparison operators -dotnet_diagnostic.MA0097.severity = none - -# RECS0129: Removes 'internal' modifiers that are not required -dotnet_diagnostic.RECS0129.severity = none - -# RECS0145: Removes 'private' modifiers that are not required -dotnet_diagnostic.RECS0145.severity = none - -# RECS0154: Parameter is never used -dotnet_diagnostic.RECS0154.severity = none - -# SA1009: Closing parenthesis should be spaced correctly -dotnet_diagnostic.SA1009.severity = none - -# SA1010: Opening square brackets should not be preceded by a space. -dotnet_diagnostic.SA1010.severity = none - -# SA1011: Closing square brackets should be spaced correctly -dotnet_diagnostic.SA1011.severity = none - -# SA1101: Prefix local calls with this -dotnet_diagnostic.SA1101.severity = none - -# SA1111: Closing parenthesis should be on line of last parameter -dotnet_diagnostic.SA1111.severity = none - -# SA1116: Split parameters should start on line after declaration -dotnet_diagnostic.SA1116.severity = none - -# SA1117: Parameters should be on same line or separate lines -dotnet_diagnostic.SA1117.severity = none - -# SA1118: Parameter should not span multiple lines -dotnet_diagnostic.SA1118.severity = none - -# SA1127: Generic type constraints should be on their own line -dotnet_diagnostic.SA1127.severity = none - -# SA1201: Elements should appear in the correct order -dotnet_diagnostic.SA1201.severity = none - -# SA1202: Elements should be ordered by access -dotnet_diagnostic.SA1202.severity = none - -# SA1204: Static elements should appear before instance elements -dotnet_diagnostic.SA1204.severity = none - -# SA1402: File may only contain a single type -dotnet_diagnostic.SA1402.severity = none - -# SA1413: Use trailing comma in multi-line initializers -dotnet_diagnostic.SA1413.severity = none - -# SA1515: Single-line comment should be preceded by blank line -dotnet_diagnostic.SA1515.severity = none - -# SA1516: Elements should be separated by blank line -dotnet_diagnostic.SA1516.severity = none - -# SA1600: Elements should be documented -dotnet_diagnostic.SA1600.severity = none - -# SA1601: Partial elements should be documented -dotnet_diagnostic.SA1601.severity = none - -# SA1602: Enumeration items should be documented -dotnet_diagnostic.SA1602.severity = none - -# SA1615: Element return value should be documented -dotnet_diagnostic.SA1615.severity = none - -# SA1623: Property summary documentation should match accessors -dotnet_diagnostic.SA1623.severity = none - -# SKEXP0001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. -dotnet_diagnostic.SKEXP0001.severity = none - -# SKEXP0003: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +# NOTE: Requires **VS2019 16.3** or later + +# Squidex Rules +# Description: Squidex Rules + +[*.json] +indent_style = space +indent_size = 4 + +[*.cs] +charset = utf-8-bom +insert_final_newline = true +indent_style = space +indent_size = 4 + +MA0015.consider_member_access_as_parameter = true + +csharp_style_namespace_declarations = file_scoped + +# FAILING ANALYZERS +dotnet_diagnostic.RECS0002.severity = none +dotnet_diagnostic.RECS0117.severity = none +dotnet_diagnostic.SA0001.severity = none +dotnet_diagnostic.SA1649.severity = none + +# IDE0305: Simplify collection initialization +dotnet_diagnostic.IDE0305.severity = none + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1707.severity = none + +# CA2016: Forward the 'CancellationToken' parameter to methods +dotnet_diagnostic.CA2016.severity = none + +# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +dotnet_diagnostic.CS8618.severity = none + +# IDE0063: Use simple 'using' statement +dotnet_diagnostic.IDE0063.severity = none + +# IDE0066: Convert switch statement to expression +dotnet_diagnostic.IDE0066.severity = none + +# IDE0090: Use 'new(...)' +dotnet_diagnostic.IDE0090.severity = none + +# MA0002: IEqualityComparer or IComparer is missing +dotnet_diagnostic.MA0002.severity = none + +# MA0003: Add argument name to improve readability +dotnet_diagnostic.MA0003.severity = none + +# MA0004: Use Task.ConfigureAwait(false) +dotnet_diagnostic.MA0004.severity = none + +# MA0006: Use String.Equals instead of equality operator +dotnet_diagnostic.MA0006.severity = none + +# MA0007: Add a comma after the last value +dotnet_diagnostic.MA0007.severity = warning + +# MA0008: Add StructLayoutAttribute +dotnet_diagnostic.MA0008.severity = none + +# MA0009: Add regex evaluation timeout +dotnet_diagnostic.MA0009.severity = none + +# MA0016: Prefer return collection abstraction instead of implementation +dotnet_diagnostic.MA0016.severity = none + +# MA0018: Do not declare static members on generic types +dotnet_diagnostic.MA0018.severity = none + +# MA0025: Implement the functionality instead of throwing NotImplementedException +dotnet_diagnostic.MA0025.severity = none + +# MA0028: Optimize StringBuilder usage +dotnet_diagnostic.MA0028.severity = none + +# MA0029: Combine LINQ methods +dotnet_diagnostic.MA0029.severity = none + +# MA0031: Optimize Enumerable.Count() usage +dotnet_diagnostic.MA0031.severity = none + +# MA0036: Make class static +dotnet_diagnostic.MA0036.severity = none + +# MA0038: Make method static +dotnet_diagnostic.MA0038.severity = none + +# MA0039: Do not write your own certificate validation method +dotnet_diagnostic.MA0039.severity = none + +# MA0049: Type name should not match containing namespace +dotnet_diagnostic.MA0049.severity = none + +# MA0051: Method is too long +dotnet_diagnostic.MA0051.severity = none + +# MA0069: Non-constant static fields should not be visible +dotnet_diagnostic.MA0069.severity = none + +# MA0071: Avoid using redundant else +dotnet_diagnostic.MA0071.severity = none + +# MA0076: Do not use implicit culture-sensitive ToString in interpolated strings +dotnet_diagnostic.MA0076.severity = none + +# MA0097: A class that implements IComparable or IComparable should override comparison operators +dotnet_diagnostic.MA0097.severity = none + +# RECS0129: Removes 'internal' modifiers that are not required +dotnet_diagnostic.RECS0129.severity = none + +# RECS0145: Removes 'private' modifiers that are not required +dotnet_diagnostic.RECS0145.severity = none + +# RECS0154: Parameter is never used +dotnet_diagnostic.RECS0154.severity = none + +# SA1009: Closing parenthesis should be spaced correctly +dotnet_diagnostic.SA1009.severity = none + +# SA1010: Opening square brackets should not be preceded by a space. +dotnet_diagnostic.SA1010.severity = none + +# SA1011: Closing square brackets should be spaced correctly +dotnet_diagnostic.SA1011.severity = none + +# SA1101: Prefix local calls with this +dotnet_diagnostic.SA1101.severity = none + +# SA1111: Closing parenthesis should be on line of last parameter +dotnet_diagnostic.SA1111.severity = none + +# SA1116: Split parameters should start on line after declaration +dotnet_diagnostic.SA1116.severity = none + +# SA1117: Parameters should be on same line or separate lines +dotnet_diagnostic.SA1117.severity = none + +# SA1118: Parameter should not span multiple lines +dotnet_diagnostic.SA1118.severity = none + +# SA1127: Generic type constraints should be on their own line +dotnet_diagnostic.SA1127.severity = none + +# SA1201: Elements should appear in the correct order +dotnet_diagnostic.SA1201.severity = none + +# SA1202: Elements should be ordered by access +dotnet_diagnostic.SA1202.severity = none + +# SA1204: Static elements should appear before instance elements +dotnet_diagnostic.SA1204.severity = none + +# SA1402: File may only contain a single type +dotnet_diagnostic.SA1402.severity = none + +# SA1413: Use trailing comma in multi-line initializers +dotnet_diagnostic.SA1413.severity = none + +# SA1515: Single-line comment should be preceded by blank line +dotnet_diagnostic.SA1515.severity = none + +# SA1516: Elements should be separated by blank line +dotnet_diagnostic.SA1516.severity = none + +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none + +# SA1601: Partial elements should be documented +dotnet_diagnostic.SA1601.severity = none + +# SA1602: Enumeration items should be documented +dotnet_diagnostic.SA1602.severity = none + +# SA1615: Element return value should be documented +dotnet_diagnostic.SA1615.severity = none + +# SA1623: Property summary documentation should match accessors +dotnet_diagnostic.SA1623.severity = none + +# SKEXP0001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +dotnet_diagnostic.SKEXP0001.severity = none + +# SKEXP0003: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. dotnet_diagnostic.SKEXP0003.severity = none \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index feb9b3c..66c3dba 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,44 +1,44 @@ -name: Nuget Build - -on: - push: - branches: - - 'main' - pull_request: - branches: - - 'main' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4.2.2 - - - name: setup dotnet - id: dotnet - uses: actions/setup-dotnet@v4.3.1 - with: - dotnet-version: 8.0.x - - # Note: Unless a concrete version is specified in the `global.json` file, - # the latest .NET version installed on the runner (including preinstalled - # versions) will be used by default. To control this behavior, you may want - # to use temporary `global.json` files. - # https://github.com/actions/setup-dotnet/blob/main/README.md#matrix-testing - - name: Create `global.json` - run: | - echo '{"sdk":{"version": "${{ steps.dotnet.outputs.dotnet-version }}"}}' > ./global.json - - - name: test - timeout-minutes: 10 - run: | - dotnet test --filter Category!=Dependencies - - - name: pack - run: | - dotnet pack -c Release - - - name: publish - run: | - dotnet nuget push **/*.nupkg --source 'https://api.nuget.org/v3/index.json' --skip-duplicate -k ${{ secrets.nuget }} +name: Nuget Build + +on: + push: + branches: + - 'main' + pull_request: + branches: + - 'main' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.2.2 + + - name: setup dotnet + id: dotnet + uses: actions/setup-dotnet@v4.3.1 + with: + dotnet-version: 10.0.x + + # Note: Unless a concrete version is specified in the `global.json` file, + # the latest .NET version installed on the runner (including preinstalled + # versions) will be used by default. To control this behavior, you may want + # to use temporary `global.json` files. + # https://github.com/actions/setup-dotnet/blob/main/README.md#matrix-testing + - name: Create `global.json` + run: | + echo '{"sdk":{"version": "${{ steps.dotnet.outputs.dotnet-version }}"}}' > ./global.json + + - name: test + timeout-minutes: 10 + run: | + dotnet test --filter Category!=Dependencies + + - name: pack + run: | + dotnet pack -c Release + + - name: publish + run: | + dotnet nuget push **/Squidex*.nupkg --source 'https://api.nuget.org/v3/index.json' --skip-duplicate -k ${{ secrets.nuget }} diff --git a/Directory.Build.props b/Directory.Build.props index 2d26d1f..c0e2df5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ https://github.com/squidex/squidex true snupkg - 7.37.0 + 8.0.0 diff --git a/Squidex.Libs.sln b/Squidex.Libs.sln deleted file mode 100644 index 5f4e509..0000000 --- a/Squidex.Libs.sln +++ /dev/null @@ -1,381 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "text", "text", "{A547A63E-8D7A-4E5B-ACE2-BEE634E194DE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Text", "text\Squidex.Text\Squidex.Text.csproj", "{E4A3801B-40B3-4794-840B-9841A45FA8FD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Text.Tests", "text\Squidex.Text.Tests\Squidex.Text.Tests.csproj", "{D435A0DF-60B3-4F9D-A23F-EB51A8435AEF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hosting", "hosting", "{39C995C3-B2C0-45FD-9686-4620A9F92677}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Hosting", "hosting\Squidex.Hosting\Squidex.Hosting.csproj", "{F3F14E6B-DFC9-45AA-A8A3-BA061D134E5E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Hosting.Abstractions", "hosting\Squidex.Hosting.Abstractions\Squidex.Hosting.Abstractions.csproj", "{F6564F0D-F63D-42A8-B1F2-FECF07309174}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Hosting.TestRunner", "hosting\Squidex.Hosting.TestRunner\Squidex.Hosting.TestRunner.csproj", "{19886529-479B-4166-8C49-1B4304EE8469}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Hosting.Tests", "hosting\Squidex.Hosting.Tests\Squidex.Hosting.Tests.csproj", "{87EB4749-78E0-49A7-9364-B0FD65358DE0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "log", "log", "{576880B0-A648-40EC-ACE1-7C98752FFF00}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Log", "log\Squidex.Log\Squidex.Log.csproj", "{A830F88B-B120-4F14-85B5-167C142146C4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Log.Tests", "log\Squidex.Log.Tests\Squidex.Log.Tests.csproj", "{FA4949E4-B48C-47A6-B5E4-60039063EC03}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "messaging", "messaging", "{28B7D0BB-1971-4802-BC40-28297D644B26}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging", "messaging\Squidex.Messaging\Squidex.Messaging.csproj", "{A85ECE5A-AD79-46C2-9859-B22244D70B8F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.All", "messaging\Squidex.Messaging.All\Squidex.Messaging.All.csproj", "{8F60DA58-14AD-4252-821D-903C74BAF7C7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.GoogleCloud", "messaging\Squidex.Messaging.GoogleCloud\Squidex.Messaging.GoogleCloud.csproj", "{02E75A8A-722A-4EF0-8323-9346CCE8131F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.Kafka", "messaging\Squidex.Messaging.Kafka\Squidex.Messaging.Kafka.csproj", "{12CCCE8E-22C0-4B3D-A627-D5E16AED0FE8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.Mongo", "messaging\Squidex.Messaging.Mongo\Squidex.Messaging.Mongo.csproj", "{32277F23-625A-4390-AC10-951BDC4EEF92}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.RabbitMq", "messaging\Squidex.Messaging.RabbitMq\Squidex.Messaging.RabbitMq.csproj", "{1CA0326B-82A4-437E-8B7A-06BBC72BCDFE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.Redis", "messaging\Squidex.Messaging.Redis\Squidex.Messaging.Redis.csproj", "{B29516FF-08E3-45C3-A127-ADA72F771375}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.Subscriptions", "messaging\Squidex.Messaging.Subscriptions\Squidex.Messaging.Subscriptions.csproj", "{1C783753-4AF3-481F-A11D-640A92D64B47}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "caching", "caching", "{388AA38D-B40C-4D92-8B4E-89FB85E4E495}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Caching", "caching\Squidex.Caching\Squidex.Caching.csproj", "{B4363C63-6E2E-4B1F-8B91-99C932D239AD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Caching.Tests", "caching\Squidex.Caching.Tests\Squidex.Caching.Tests.csproj", "{ECAA13A1-45A2-4821-9A26-2E7F274925E6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{C857F3ED-A6AE-47C6-A115-87ECCB36AC02}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "assets\Benchmarks\Benchmarks.csproj", "{CD8FFFA2-482E-4720-85B1-636B8693C730}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets", "assets\Squidex.Assets\Squidex.Assets.csproj", "{41E66682-EE7B-4344-BDE1-FB24EBD131BC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.Azure", "assets\Squidex.Assets.Azure\Squidex.Assets.Azure.csproj", "{6AE0BD56-83C5-490B-BEB5-0CEFC2F82F80}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.GoogleCloud", "assets\Squidex.Assets.GoogleCloud\Squidex.Assets.GoogleCloud.csproj", "{2089DB36-EA45-4B43-A890-50CFBE08BEEC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.FTP", "assets\Squidex.Assets.FTP\Squidex.Assets.FTP.csproj", "{910D7EC6-F60D-4DE6-BDD6-4BBFC2755E73}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.ImageMagick", "assets\Squidex.Assets.ImageMagick\Squidex.Assets.ImageMagick.csproj", "{03158C74-6AA3-43D6-BBD1-DE8072AC67D6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.ImageSharp", "assets\Squidex.Assets.ImageSharp\Squidex.Assets.ImageSharp.csproj", "{F73D6CB8-C728-4B42-8085-68C840515C71}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.Mongo", "assets\Squidex.Assets.Mongo\Squidex.Assets.Mongo.csproj", "{ACCAEB5A-FA4D-459A-90E0-8B4154FB5BE9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.ResizeService", "assets\Squidex.Assets.ResizeService\Squidex.Assets.ResizeService.csproj", "{914AEAFC-955A-4F0A-ACBB-F5BFB0A947D3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.S3", "assets\Squidex.Assets.S3\Squidex.Assets.S3.csproj", "{8FC2C5D1-F3C5-4C1B-B544-C31117350066}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.TusAdapter", "assets\Squidex.Assets.TusAdapter\Squidex.Assets.TusAdapter.csproj", "{80542EFC-E04D-496F-ABCB-54D5B4DA0199}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.TusClient", "assets\Squidex.Assets.TusClient\Squidex.Assets.TusClient.csproj", "{27DD4714-3510-4182-AB3A-C1305EDA57DA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TusTestServer", "assets\TusTestServer\TusTestServer.csproj", "{04F2D248-DDF2-4B53-BF03-904F204CD696}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.Tests", "messaging\Squidex.Messaging.Tests\Squidex.Messaging.Tests.csproj", "{416E866B-8B41-4B5C-B919-8162C8044534}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Assets.Tests", "assets\Squidex.Assets.Tests\Squidex.Assets.Tests.csproj", "{B4461E6B-81ED-4C3D-86D6-03C2B367DB15}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai", "ai", "{F18E275B-4805-4DCB-BE31-ACC314FB508E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.AI", "ai\Squidex.AI\Squidex.AI.csproj", "{A4EAB4B8-096D-4F4F-85E1-A1385B26680B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.AI.Tests", "ai\Squidex.AI.Tests\Squidex.AI.Tests.csproj", "{AD46BEF0-33C8-4994-B242-0D9E4C50488F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "flows", "flows", "{67DB170D-E43B-431F-AE3A-2730FC631D12}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Flows", "flows\Squidex.Flows\Squidex.Flows.csproj", "{A11B0218-3FCB-46F4-B3B6-B56A36BA02BC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Flows.Tests", "flows\Squidex.Flows.Tests\Squidex.Flows.Tests.csproj", "{CC9096C3-1FAD-42CD-AA50-622BE68BC78D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Flows.Mongo", "flows\Squidex.Flows.Mongo\Squidex.Flows.Mongo.csproj", "{559B7D03-755C-4233-A055-670ABF8D4D58}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.AI.Mongo", "ai\Squidex.AI.Mongo\Squidex.AI.Mongo.csproj", "{98AE3491-7D34-498B-8A8F-14BDAAF37AD3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.AI.EntityFramework", "ai\Squidex.AI.EntityFramework\Squidex.AI.EntityFramework.csproj", "{F15850C7-D623-4CBE-ABE0-07D9822B9326}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Messaging.EntityFramework", "messaging\Squidex.Messaging.EntityFramework\Squidex.Messaging.EntityFramework.csproj", "{F653DF48-6ED7-45A0-B630-88E783202A64}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "events", "events", "{94285572-6875-4A9C-AFC4-987758DC9088}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Events", "events\Squidex.Events\Squidex.Events.csproj", "{CD62FC9A-42CC-45C8-B3FC-CB72694AE037}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Events.EntityFramework", "events\Squidex.Events.EntityFramework\Squidex.Events.EntityFramework.csproj", "{5F4B51E1-0ADD-4C03-A93A-401BA86D08BA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Events.Tests", "events\Squidex.Events.Tests\Squidex.Events.Tests.csproj", "{1E9B31E9-EA9D-4A82-B207-00E8B275EFD4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Events.Mongo", "events\Squidex.Events.Mongo\Squidex.Events.Mongo.csproj", "{E36DAC0D-8A2D-49AA-B9C4-E74CAB0660B0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Events.GetEventStore", "events\Squidex.Events.GetEventStore\Squidex.Events.GetEventStore.csproj", "{98156A5E-1B4A-46EF-AA84-019868425D80}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Assets.EntityFramework", "assets\Squidex.Assets.EntityFramework\Squidex.Assets.EntityFramework.csproj", "{C3D35ED4-8C81-449B-B732-3F0EE1FD6C7A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Flows.EntityFramework", "flows\Squidex.Flows.EntityFramework\Squidex.Flows.EntityFramework.csproj", "{A38F612A-BF14-45E5-B025-D7218074E154}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestHelpers", "utils\TestHelpers\TestHelpers.csproj", "{4E93A424-2190-491C-909F-40A5A06F36B7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E4A3801B-40B3-4794-840B-9841A45FA8FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E4A3801B-40B3-4794-840B-9841A45FA8FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4A3801B-40B3-4794-840B-9841A45FA8FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E4A3801B-40B3-4794-840B-9841A45FA8FD}.Release|Any CPU.Build.0 = Release|Any CPU - {D435A0DF-60B3-4F9D-A23F-EB51A8435AEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D435A0DF-60B3-4F9D-A23F-EB51A8435AEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D435A0DF-60B3-4F9D-A23F-EB51A8435AEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D435A0DF-60B3-4F9D-A23F-EB51A8435AEF}.Release|Any CPU.Build.0 = Release|Any CPU - {F3F14E6B-DFC9-45AA-A8A3-BA061D134E5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3F14E6B-DFC9-45AA-A8A3-BA061D134E5E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3F14E6B-DFC9-45AA-A8A3-BA061D134E5E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3F14E6B-DFC9-45AA-A8A3-BA061D134E5E}.Release|Any CPU.Build.0 = Release|Any CPU - {F6564F0D-F63D-42A8-B1F2-FECF07309174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F6564F0D-F63D-42A8-B1F2-FECF07309174}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F6564F0D-F63D-42A8-B1F2-FECF07309174}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F6564F0D-F63D-42A8-B1F2-FECF07309174}.Release|Any CPU.Build.0 = Release|Any CPU - {19886529-479B-4166-8C49-1B4304EE8469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19886529-479B-4166-8C49-1B4304EE8469}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19886529-479B-4166-8C49-1B4304EE8469}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19886529-479B-4166-8C49-1B4304EE8469}.Release|Any CPU.Build.0 = Release|Any CPU - {87EB4749-78E0-49A7-9364-B0FD65358DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87EB4749-78E0-49A7-9364-B0FD65358DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87EB4749-78E0-49A7-9364-B0FD65358DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87EB4749-78E0-49A7-9364-B0FD65358DE0}.Release|Any CPU.Build.0 = Release|Any CPU - {A830F88B-B120-4F14-85B5-167C142146C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A830F88B-B120-4F14-85B5-167C142146C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A830F88B-B120-4F14-85B5-167C142146C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A830F88B-B120-4F14-85B5-167C142146C4}.Release|Any CPU.Build.0 = Release|Any CPU - {FA4949E4-B48C-47A6-B5E4-60039063EC03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA4949E4-B48C-47A6-B5E4-60039063EC03}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA4949E4-B48C-47A6-B5E4-60039063EC03}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA4949E4-B48C-47A6-B5E4-60039063EC03}.Release|Any CPU.Build.0 = Release|Any CPU - {A85ECE5A-AD79-46C2-9859-B22244D70B8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A85ECE5A-AD79-46C2-9859-B22244D70B8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A85ECE5A-AD79-46C2-9859-B22244D70B8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A85ECE5A-AD79-46C2-9859-B22244D70B8F}.Release|Any CPU.Build.0 = Release|Any CPU - {8F60DA58-14AD-4252-821D-903C74BAF7C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8F60DA58-14AD-4252-821D-903C74BAF7C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F60DA58-14AD-4252-821D-903C74BAF7C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8F60DA58-14AD-4252-821D-903C74BAF7C7}.Release|Any CPU.Build.0 = Release|Any CPU - {02E75A8A-722A-4EF0-8323-9346CCE8131F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02E75A8A-722A-4EF0-8323-9346CCE8131F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02E75A8A-722A-4EF0-8323-9346CCE8131F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02E75A8A-722A-4EF0-8323-9346CCE8131F}.Release|Any CPU.Build.0 = Release|Any CPU - {12CCCE8E-22C0-4B3D-A627-D5E16AED0FE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12CCCE8E-22C0-4B3D-A627-D5E16AED0FE8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12CCCE8E-22C0-4B3D-A627-D5E16AED0FE8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12CCCE8E-22C0-4B3D-A627-D5E16AED0FE8}.Release|Any CPU.Build.0 = Release|Any CPU - {32277F23-625A-4390-AC10-951BDC4EEF92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32277F23-625A-4390-AC10-951BDC4EEF92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32277F23-625A-4390-AC10-951BDC4EEF92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32277F23-625A-4390-AC10-951BDC4EEF92}.Release|Any CPU.Build.0 = Release|Any CPU - {1CA0326B-82A4-437E-8B7A-06BBC72BCDFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1CA0326B-82A4-437E-8B7A-06BBC72BCDFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1CA0326B-82A4-437E-8B7A-06BBC72BCDFE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1CA0326B-82A4-437E-8B7A-06BBC72BCDFE}.Release|Any CPU.Build.0 = Release|Any CPU - {B29516FF-08E3-45C3-A127-ADA72F771375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B29516FF-08E3-45C3-A127-ADA72F771375}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B29516FF-08E3-45C3-A127-ADA72F771375}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B29516FF-08E3-45C3-A127-ADA72F771375}.Release|Any CPU.Build.0 = Release|Any CPU - {1C783753-4AF3-481F-A11D-640A92D64B47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C783753-4AF3-481F-A11D-640A92D64B47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C783753-4AF3-481F-A11D-640A92D64B47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C783753-4AF3-481F-A11D-640A92D64B47}.Release|Any CPU.Build.0 = Release|Any CPU - {B4363C63-6E2E-4B1F-8B91-99C932D239AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4363C63-6E2E-4B1F-8B91-99C932D239AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B4363C63-6E2E-4B1F-8B91-99C932D239AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B4363C63-6E2E-4B1F-8B91-99C932D239AD}.Release|Any CPU.Build.0 = Release|Any CPU - {ECAA13A1-45A2-4821-9A26-2E7F274925E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ECAA13A1-45A2-4821-9A26-2E7F274925E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECAA13A1-45A2-4821-9A26-2E7F274925E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ECAA13A1-45A2-4821-9A26-2E7F274925E6}.Release|Any CPU.Build.0 = Release|Any CPU - {CD8FFFA2-482E-4720-85B1-636B8693C730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD8FFFA2-482E-4720-85B1-636B8693C730}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD8FFFA2-482E-4720-85B1-636B8693C730}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD8FFFA2-482E-4720-85B1-636B8693C730}.Release|Any CPU.Build.0 = Release|Any CPU - {41E66682-EE7B-4344-BDE1-FB24EBD131BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41E66682-EE7B-4344-BDE1-FB24EBD131BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41E66682-EE7B-4344-BDE1-FB24EBD131BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41E66682-EE7B-4344-BDE1-FB24EBD131BC}.Release|Any CPU.Build.0 = Release|Any CPU - {6AE0BD56-83C5-490B-BEB5-0CEFC2F82F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AE0BD56-83C5-490B-BEB5-0CEFC2F82F80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AE0BD56-83C5-490B-BEB5-0CEFC2F82F80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AE0BD56-83C5-490B-BEB5-0CEFC2F82F80}.Release|Any CPU.Build.0 = Release|Any CPU - {2089DB36-EA45-4B43-A890-50CFBE08BEEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2089DB36-EA45-4B43-A890-50CFBE08BEEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2089DB36-EA45-4B43-A890-50CFBE08BEEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2089DB36-EA45-4B43-A890-50CFBE08BEEC}.Release|Any CPU.Build.0 = Release|Any CPU - {910D7EC6-F60D-4DE6-BDD6-4BBFC2755E73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {910D7EC6-F60D-4DE6-BDD6-4BBFC2755E73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {910D7EC6-F60D-4DE6-BDD6-4BBFC2755E73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {910D7EC6-F60D-4DE6-BDD6-4BBFC2755E73}.Release|Any CPU.Build.0 = Release|Any CPU - {03158C74-6AA3-43D6-BBD1-DE8072AC67D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03158C74-6AA3-43D6-BBD1-DE8072AC67D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03158C74-6AA3-43D6-BBD1-DE8072AC67D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03158C74-6AA3-43D6-BBD1-DE8072AC67D6}.Release|Any CPU.Build.0 = Release|Any CPU - {F73D6CB8-C728-4B42-8085-68C840515C71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F73D6CB8-C728-4B42-8085-68C840515C71}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F73D6CB8-C728-4B42-8085-68C840515C71}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F73D6CB8-C728-4B42-8085-68C840515C71}.Release|Any CPU.Build.0 = Release|Any CPU - {ACCAEB5A-FA4D-459A-90E0-8B4154FB5BE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ACCAEB5A-FA4D-459A-90E0-8B4154FB5BE9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ACCAEB5A-FA4D-459A-90E0-8B4154FB5BE9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ACCAEB5A-FA4D-459A-90E0-8B4154FB5BE9}.Release|Any CPU.Build.0 = Release|Any CPU - {914AEAFC-955A-4F0A-ACBB-F5BFB0A947D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {914AEAFC-955A-4F0A-ACBB-F5BFB0A947D3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {914AEAFC-955A-4F0A-ACBB-F5BFB0A947D3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {914AEAFC-955A-4F0A-ACBB-F5BFB0A947D3}.Release|Any CPU.Build.0 = Release|Any CPU - {8FC2C5D1-F3C5-4C1B-B544-C31117350066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8FC2C5D1-F3C5-4C1B-B544-C31117350066}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8FC2C5D1-F3C5-4C1B-B544-C31117350066}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8FC2C5D1-F3C5-4C1B-B544-C31117350066}.Release|Any CPU.Build.0 = Release|Any CPU - {80542EFC-E04D-496F-ABCB-54D5B4DA0199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80542EFC-E04D-496F-ABCB-54D5B4DA0199}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80542EFC-E04D-496F-ABCB-54D5B4DA0199}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80542EFC-E04D-496F-ABCB-54D5B4DA0199}.Release|Any CPU.Build.0 = Release|Any CPU - {27DD4714-3510-4182-AB3A-C1305EDA57DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27DD4714-3510-4182-AB3A-C1305EDA57DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27DD4714-3510-4182-AB3A-C1305EDA57DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27DD4714-3510-4182-AB3A-C1305EDA57DA}.Release|Any CPU.Build.0 = Release|Any CPU - {04F2D248-DDF2-4B53-BF03-904F204CD696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04F2D248-DDF2-4B53-BF03-904F204CD696}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04F2D248-DDF2-4B53-BF03-904F204CD696}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04F2D248-DDF2-4B53-BF03-904F204CD696}.Release|Any CPU.Build.0 = Release|Any CPU - {416E866B-8B41-4B5C-B919-8162C8044534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {416E866B-8B41-4B5C-B919-8162C8044534}.Debug|Any CPU.Build.0 = Debug|Any CPU - {416E866B-8B41-4B5C-B919-8162C8044534}.Release|Any CPU.ActiveCfg = Release|Any CPU - {416E866B-8B41-4B5C-B919-8162C8044534}.Release|Any CPU.Build.0 = Release|Any CPU - {B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B4461E6B-81ED-4C3D-86D6-03C2B367DB15}.Release|Any CPU.Build.0 = Release|Any CPU - {A4EAB4B8-096D-4F4F-85E1-A1385B26680B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4EAB4B8-096D-4F4F-85E1-A1385B26680B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4EAB4B8-096D-4F4F-85E1-A1385B26680B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4EAB4B8-096D-4F4F-85E1-A1385B26680B}.Release|Any CPU.Build.0 = Release|Any CPU - {AD46BEF0-33C8-4994-B242-0D9E4C50488F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD46BEF0-33C8-4994-B242-0D9E4C50488F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD46BEF0-33C8-4994-B242-0D9E4C50488F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD46BEF0-33C8-4994-B242-0D9E4C50488F}.Release|Any CPU.Build.0 = Release|Any CPU - {A11B0218-3FCB-46F4-B3B6-B56A36BA02BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A11B0218-3FCB-46F4-B3B6-B56A36BA02BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A11B0218-3FCB-46F4-B3B6-B56A36BA02BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A11B0218-3FCB-46F4-B3B6-B56A36BA02BC}.Release|Any CPU.Build.0 = Release|Any CPU - {CC9096C3-1FAD-42CD-AA50-622BE68BC78D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC9096C3-1FAD-42CD-AA50-622BE68BC78D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC9096C3-1FAD-42CD-AA50-622BE68BC78D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC9096C3-1FAD-42CD-AA50-622BE68BC78D}.Release|Any CPU.Build.0 = Release|Any CPU - {559B7D03-755C-4233-A055-670ABF8D4D58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {559B7D03-755C-4233-A055-670ABF8D4D58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {559B7D03-755C-4233-A055-670ABF8D4D58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {559B7D03-755C-4233-A055-670ABF8D4D58}.Release|Any CPU.Build.0 = Release|Any CPU - {98AE3491-7D34-498B-8A8F-14BDAAF37AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98AE3491-7D34-498B-8A8F-14BDAAF37AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98AE3491-7D34-498B-8A8F-14BDAAF37AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98AE3491-7D34-498B-8A8F-14BDAAF37AD3}.Release|Any CPU.Build.0 = Release|Any CPU - {F15850C7-D623-4CBE-ABE0-07D9822B9326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F15850C7-D623-4CBE-ABE0-07D9822B9326}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F15850C7-D623-4CBE-ABE0-07D9822B9326}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F15850C7-D623-4CBE-ABE0-07D9822B9326}.Release|Any CPU.Build.0 = Release|Any CPU - {F653DF48-6ED7-45A0-B630-88E783202A64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F653DF48-6ED7-45A0-B630-88E783202A64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F653DF48-6ED7-45A0-B630-88E783202A64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F653DF48-6ED7-45A0-B630-88E783202A64}.Release|Any CPU.Build.0 = Release|Any CPU - {CD62FC9A-42CC-45C8-B3FC-CB72694AE037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD62FC9A-42CC-45C8-B3FC-CB72694AE037}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD62FC9A-42CC-45C8-B3FC-CB72694AE037}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD62FC9A-42CC-45C8-B3FC-CB72694AE037}.Release|Any CPU.Build.0 = Release|Any CPU - {5F4B51E1-0ADD-4C03-A93A-401BA86D08BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F4B51E1-0ADD-4C03-A93A-401BA86D08BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F4B51E1-0ADD-4C03-A93A-401BA86D08BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F4B51E1-0ADD-4C03-A93A-401BA86D08BA}.Release|Any CPU.Build.0 = Release|Any CPU - {1E9B31E9-EA9D-4A82-B207-00E8B275EFD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E9B31E9-EA9D-4A82-B207-00E8B275EFD4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E9B31E9-EA9D-4A82-B207-00E8B275EFD4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E9B31E9-EA9D-4A82-B207-00E8B275EFD4}.Release|Any CPU.Build.0 = Release|Any CPU - {E36DAC0D-8A2D-49AA-B9C4-E74CAB0660B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E36DAC0D-8A2D-49AA-B9C4-E74CAB0660B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E36DAC0D-8A2D-49AA-B9C4-E74CAB0660B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E36DAC0D-8A2D-49AA-B9C4-E74CAB0660B0}.Release|Any CPU.Build.0 = Release|Any CPU - {98156A5E-1B4A-46EF-AA84-019868425D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98156A5E-1B4A-46EF-AA84-019868425D80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98156A5E-1B4A-46EF-AA84-019868425D80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98156A5E-1B4A-46EF-AA84-019868425D80}.Release|Any CPU.Build.0 = Release|Any CPU - {C3D35ED4-8C81-449B-B732-3F0EE1FD6C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3D35ED4-8C81-449B-B732-3F0EE1FD6C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3D35ED4-8C81-449B-B732-3F0EE1FD6C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3D35ED4-8C81-449B-B732-3F0EE1FD6C7A}.Release|Any CPU.Build.0 = Release|Any CPU - {A38F612A-BF14-45E5-B025-D7218074E154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A38F612A-BF14-45E5-B025-D7218074E154}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A38F612A-BF14-45E5-B025-D7218074E154}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A38F612A-BF14-45E5-B025-D7218074E154}.Release|Any CPU.Build.0 = Release|Any CPU - {4E93A424-2190-491C-909F-40A5A06F36B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E93A424-2190-491C-909F-40A5A06F36B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E93A424-2190-491C-909F-40A5A06F36B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E93A424-2190-491C-909F-40A5A06F36B7}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {E4A3801B-40B3-4794-840B-9841A45FA8FD} = {A547A63E-8D7A-4E5B-ACE2-BEE634E194DE} - {D435A0DF-60B3-4F9D-A23F-EB51A8435AEF} = {A547A63E-8D7A-4E5B-ACE2-BEE634E194DE} - {F3F14E6B-DFC9-45AA-A8A3-BA061D134E5E} = {39C995C3-B2C0-45FD-9686-4620A9F92677} - {F6564F0D-F63D-42A8-B1F2-FECF07309174} = {39C995C3-B2C0-45FD-9686-4620A9F92677} - {19886529-479B-4166-8C49-1B4304EE8469} = {39C995C3-B2C0-45FD-9686-4620A9F92677} - {87EB4749-78E0-49A7-9364-B0FD65358DE0} = {39C995C3-B2C0-45FD-9686-4620A9F92677} - {A830F88B-B120-4F14-85B5-167C142146C4} = {576880B0-A648-40EC-ACE1-7C98752FFF00} - {FA4949E4-B48C-47A6-B5E4-60039063EC03} = {576880B0-A648-40EC-ACE1-7C98752FFF00} - {A85ECE5A-AD79-46C2-9859-B22244D70B8F} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {8F60DA58-14AD-4252-821D-903C74BAF7C7} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {02E75A8A-722A-4EF0-8323-9346CCE8131F} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {12CCCE8E-22C0-4B3D-A627-D5E16AED0FE8} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {32277F23-625A-4390-AC10-951BDC4EEF92} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {1CA0326B-82A4-437E-8B7A-06BBC72BCDFE} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {B29516FF-08E3-45C3-A127-ADA72F771375} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {1C783753-4AF3-481F-A11D-640A92D64B47} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {B4363C63-6E2E-4B1F-8B91-99C932D239AD} = {388AA38D-B40C-4D92-8B4E-89FB85E4E495} - {ECAA13A1-45A2-4821-9A26-2E7F274925E6} = {388AA38D-B40C-4D92-8B4E-89FB85E4E495} - {CD8FFFA2-482E-4720-85B1-636B8693C730} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {41E66682-EE7B-4344-BDE1-FB24EBD131BC} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {6AE0BD56-83C5-490B-BEB5-0CEFC2F82F80} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {2089DB36-EA45-4B43-A890-50CFBE08BEEC} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {910D7EC6-F60D-4DE6-BDD6-4BBFC2755E73} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {03158C74-6AA3-43D6-BBD1-DE8072AC67D6} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {F73D6CB8-C728-4B42-8085-68C840515C71} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {ACCAEB5A-FA4D-459A-90E0-8B4154FB5BE9} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {914AEAFC-955A-4F0A-ACBB-F5BFB0A947D3} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {8FC2C5D1-F3C5-4C1B-B544-C31117350066} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {80542EFC-E04D-496F-ABCB-54D5B4DA0199} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {27DD4714-3510-4182-AB3A-C1305EDA57DA} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {04F2D248-DDF2-4B53-BF03-904F204CD696} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {416E866B-8B41-4B5C-B919-8162C8044534} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {B4461E6B-81ED-4C3D-86D6-03C2B367DB15} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {A4EAB4B8-096D-4F4F-85E1-A1385B26680B} = {F18E275B-4805-4DCB-BE31-ACC314FB508E} - {AD46BEF0-33C8-4994-B242-0D9E4C50488F} = {F18E275B-4805-4DCB-BE31-ACC314FB508E} - {A11B0218-3FCB-46F4-B3B6-B56A36BA02BC} = {67DB170D-E43B-431F-AE3A-2730FC631D12} - {CC9096C3-1FAD-42CD-AA50-622BE68BC78D} = {67DB170D-E43B-431F-AE3A-2730FC631D12} - {559B7D03-755C-4233-A055-670ABF8D4D58} = {67DB170D-E43B-431F-AE3A-2730FC631D12} - {98AE3491-7D34-498B-8A8F-14BDAAF37AD3} = {F18E275B-4805-4DCB-BE31-ACC314FB508E} - {F15850C7-D623-4CBE-ABE0-07D9822B9326} = {F18E275B-4805-4DCB-BE31-ACC314FB508E} - {F653DF48-6ED7-45A0-B630-88E783202A64} = {28B7D0BB-1971-4802-BC40-28297D644B26} - {CD62FC9A-42CC-45C8-B3FC-CB72694AE037} = {94285572-6875-4A9C-AFC4-987758DC9088} - {5F4B51E1-0ADD-4C03-A93A-401BA86D08BA} = {94285572-6875-4A9C-AFC4-987758DC9088} - {1E9B31E9-EA9D-4A82-B207-00E8B275EFD4} = {94285572-6875-4A9C-AFC4-987758DC9088} - {E36DAC0D-8A2D-49AA-B9C4-E74CAB0660B0} = {94285572-6875-4A9C-AFC4-987758DC9088} - {98156A5E-1B4A-46EF-AA84-019868425D80} = {94285572-6875-4A9C-AFC4-987758DC9088} - {C3D35ED4-8C81-449B-B732-3F0EE1FD6C7A} = {C857F3ED-A6AE-47C6-A115-87ECCB36AC02} - {A38F612A-BF14-45E5-B025-D7218074E154} = {67DB170D-E43B-431F-AE3A-2730FC631D12} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {060512DD-34DA-4929-A67F-2E473577FBF5} - EndGlobalSection -EndGlobal diff --git a/Squidex.Libs.slnx b/Squidex.Libs.slnx new file mode 100644 index 0000000..0feccd8 --- /dev/null +++ b/Squidex.Libs.slnx @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ai/Squidex.AI.EntityFramework/EFChatEntity.cs b/ai/Squidex.AI.EntityFramework/EFChatEntity.cs index 6a83b8f..bb9d47c 100644 --- a/ai/Squidex.AI.EntityFramework/EFChatEntity.cs +++ b/ai/Squidex.AI.EntityFramework/EFChatEntity.cs @@ -1,22 +1,22 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.ComponentModel.DataAnnotations; - -namespace Squidex.AI.Mongo; - -public sealed class EFChatEntity -{ - public string Id { get; set; } - - public string Value { get; set; } - - public DateTime LastUpdated { get; set; } - - [ConcurrencyCheck] - public Guid Version { get; set; } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; + +namespace Squidex.AI.EntityFramework; + +public sealed class EFChatEntity +{ + public string Id { get; set; } + + public string Value { get; set; } + + public DateTime LastUpdated { get; set; } + + [ConcurrencyCheck] + public Guid Version { get; set; } +} diff --git a/ai/Squidex.AI.EntityFramework/EFChatStore.cs b/ai/Squidex.AI.EntityFramework/EFChatStore.cs index ab54890..9e8421c 100644 --- a/ai/Squidex.AI.EntityFramework/EFChatStore.cs +++ b/ai/Squidex.AI.EntityFramework/EFChatStore.cs @@ -1,96 +1,97 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Runtime.CompilerServices; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Squidex.AI.Implementation; - -namespace Squidex.AI.Mongo; - -public sealed class EFChatStore(IDbContextFactory dbContextFactory) : IChatStore where T : DbContext -{ - public async Task RemoveAsync(string conversationId, - CancellationToken ct) - { - ArgumentException.ThrowIfNullOrWhiteSpace(conversationId); - - await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); - - await dbContext.Set().Where(x => x.Id == conversationId) - .ExecuteDeleteAsync(ct); - } - - public async Task GetAsync(string conversationId, - CancellationToken ct) - { - ArgumentException.ThrowIfNullOrWhiteSpace(conversationId); - - await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); - - var entity = await dbContext.Set().Where(x => x.Id == conversationId).FirstOrDefaultAsync(ct); - if (entity == null) - { - return null; - } - - var conversation = JsonSerializer.Deserialize(entity.Value) ?? - throw new ChatException($"Cannot deserialize conversion with ID '{conversationId}'."); - - return conversation; - } - - public async Task StoreAsync(string conversationId, Conversation conversation, DateTime now, - CancellationToken ct) - { - ArgumentException.ThrowIfNullOrWhiteSpace(conversationId); - ArgumentNullException.ThrowIfNull(conversation); - - var json = JsonSerializer.Serialize(conversation) ?? - throw new ChatException($"Cannot serialize conversion with ID '{conversationId}'."); - - await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); - - var entity = await dbContext.Set().Where(x => x.Id == conversationId).FirstOrDefaultAsync(ct); - if (entity != null) - { - entity.LastUpdated = now; - entity.Version = Guid.NewGuid(); - entity.Value = json; - } - else - { - entity = new EFChatEntity - { - Id = conversationId, - LastUpdated = now, - Version = Guid.NewGuid(), - Value = json, - }; - - await dbContext.Set().AddAsync(entity, ct); - } - - await dbContext.SaveChangesAsync(ct); - } - - public async IAsyncEnumerable<(string Id, Conversation Value)> QueryAsync(DateTime olderThan, - [EnumeratorCancellation] CancellationToken ct) - { - await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); - - var records = dbContext.Set().Where(x => x.LastUpdated < olderThan).AsAsyncEnumerable(); - - await foreach (var entity in records.WithCancellation(ct)) - { - var conversation = JsonSerializer.Deserialize(entity.Value) ?? - throw new ChatException($"Cannot deserialize conversion with ID '{entity.Id}'."); - - yield return (entity.Id, conversation); - } - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Runtime.CompilerServices; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Squidex.AI.EntityFramework; +using Squidex.AI.Implementation; + +namespace Squidex.AI.Mongo; + +public sealed class EFChatStore(IDbContextFactory dbContextFactory) : IChatStore where T : DbContext +{ + public async Task RemoveAsync(string conversationId, + CancellationToken ct) + { + ArgumentException.ThrowIfNullOrWhiteSpace(conversationId); + + await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); + + await dbContext.Set().Where(x => x.Id == conversationId) + .ExecuteDeleteAsync(ct); + } + + public async Task GetAsync(string conversationId, + CancellationToken ct) + { + ArgumentException.ThrowIfNullOrWhiteSpace(conversationId); + + await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); + + var entity = await dbContext.Set().Where(x => x.Id == conversationId).FirstOrDefaultAsync(ct); + if (entity == null) + { + return null; + } + + var conversation = JsonSerializer.Deserialize(entity.Value) ?? + throw new ChatException($"Cannot deserialize conversion with ID '{conversationId}'."); + + return conversation; + } + + public async Task StoreAsync(string conversationId, Conversation conversation, DateTime now, + CancellationToken ct) + { + ArgumentException.ThrowIfNullOrWhiteSpace(conversationId); + ArgumentNullException.ThrowIfNull(conversation); + + var json = JsonSerializer.Serialize(conversation) ?? + throw new ChatException($"Cannot serialize conversion with ID '{conversationId}'."); + + await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); + + var entity = await dbContext.Set().Where(x => x.Id == conversationId).FirstOrDefaultAsync(ct); + if (entity != null) + { + entity.LastUpdated = now; + entity.Version = Guid.NewGuid(); + entity.Value = json; + } + else + { + entity = new EFChatEntity + { + Id = conversationId, + LastUpdated = now, + Version = Guid.NewGuid(), + Value = json, + }; + + await dbContext.Set().AddAsync(entity, ct); + } + + await dbContext.SaveChangesAsync(ct); + } + + public async IAsyncEnumerable<(string Id, Conversation Value)> QueryAsync(DateTime olderThan, + [EnumeratorCancellation] CancellationToken ct) + { + await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); + + var records = dbContext.Set().Where(x => x.LastUpdated < olderThan).AsAsyncEnumerable(); + + await foreach (var entity in records.WithCancellation(ct)) + { + var conversation = JsonSerializer.Deserialize(entity.Value) ?? + throw new ChatException($"Cannot deserialize conversion with ID '{entity.Id}'."); + + yield return (entity.Id, conversation); + } + } +} diff --git a/ai/Squidex.AI.EntityFramework/EFSchema.cs b/ai/Squidex.AI.EntityFramework/EFSchema.cs index fdcd0ea..2ca040f 100644 --- a/ai/Squidex.AI.EntityFramework/EFSchema.cs +++ b/ai/Squidex.AI.EntityFramework/EFSchema.cs @@ -1,25 +1,25 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.AI.Mongo; - -namespace Microsoft.EntityFrameworkCore; - -public static class EFSchema -{ - public static ModelBuilder UseChatStore(this ModelBuilder modelBuilder) - { - modelBuilder.Entity(b => - { - b.ToTable("Chats"); - b.HasIndex(x => x.LastUpdated); - b.Property(x => x.Id).HasMaxLength(255); - }); - - return modelBuilder; - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.AI.EntityFramework; + +namespace Microsoft.EntityFrameworkCore; + +public static class EFSchema +{ + public static ModelBuilder UseChatStore(this ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.ToTable("Chats"); + b.HasIndex(x => x.LastUpdated); + b.Property(x => x.Id).HasMaxLength(255); + }); + + return modelBuilder; + } +} diff --git a/ai/Squidex.AI.EntityFramework/Squidex.AI.EntityFramework.csproj b/ai/Squidex.AI.EntityFramework/Squidex.AI.EntityFramework.csproj index 36f036d..4687ae3 100644 --- a/ai/Squidex.AI.EntityFramework/Squidex.AI.EntityFramework.csproj +++ b/ai/Squidex.AI.EntityFramework/Squidex.AI.EntityFramework.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/ai/Squidex.AI.Mongo/Squidex.AI.Mongo.csproj b/ai/Squidex.AI.Mongo/Squidex.AI.Mongo.csproj index a99557f..79948b8 100644 --- a/ai/Squidex.AI.Mongo/Squidex.AI.Mongo.csproj +++ b/ai/Squidex.AI.Mongo/Squidex.AI.Mongo.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - diff --git a/ai/Squidex.AI.Tests/Squidex.AI.Tests.csproj b/ai/Squidex.AI.Tests/Squidex.AI.Tests.csproj index f2e2499..7319b03 100644 --- a/ai/Squidex.AI.Tests/Squidex.AI.Tests.csproj +++ b/ai/Squidex.AI.Tests/Squidex.AI.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -11,24 +11,22 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ai/Squidex.AI/Implementation/OpenAI/DallEOptions.cs b/ai/Squidex.AI/Implementation/OpenAI/DallEOptions.cs index e016d46..a817f8d 100644 --- a/ai/Squidex.AI/Implementation/OpenAI/DallEOptions.cs +++ b/ai/Squidex.AI/Implementation/OpenAI/DallEOptions.cs @@ -1,32 +1,33 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Betalgo.Ranul.OpenAI; -using Betalgo.Ranul.OpenAI.ObjectModels; - -namespace Squidex.AI.Implementation.OpenAI; - -public sealed class DallEOptions : OpenAIOptions -{ - public string? Model { get; set; } = Models.Dall_e_3; - - public string? Style { get; set; } - - public string? Size { get; set; } - - public string? Quality { get; set; } - - public string DefaultResult { get; set; } = "{ \"url\": \"{url}\" }"; - - public string PlainResult { get; set; } = "![{name}]({url})"; - - public string ImageNamePattern { get; set; } = "Generate a slugified file name for an image from the following query {query}.\\nDo not return other content."; - - public string ImagePathPattern { get; set; } = "dall-e/{IMAGE_ID}"; - - public bool DownloadImage { get; set; } = false; -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Betalgo.Ranul.OpenAI; +using Betalgo.Ranul.OpenAI.Contracts.Enums.Image; +using Betalgo.Ranul.OpenAI.ObjectModels; + +namespace Squidex.AI.Implementation.OpenAI; + +public sealed class DallEOptions : OpenAIOptions +{ + public string Model { get; set; } = Models.Dall_e_3; + + public string Style { get; set; } = ImageStyle.Natural; + + public string Size { get; set; } = ImageSize.Size1792x1024; + + public string Quality { get; set; } = ImageQuality.Medium; + + public string DefaultResult { get; set; } = "{ \"url\": \"{url}\" }"; + + public string PlainResult { get; set; } = "![{name}]({url})"; + + public string ImageNamePattern { get; set; } = "Generate a slugified file name for an image from the following query {query}.\\nDo not return other content."; + + public string ImagePathPattern { get; set; } = "dall-e/{IMAGE_ID}"; + + public bool DownloadImage { get; set; } = false; +} diff --git a/ai/Squidex.AI/Implementation/OpenAI/DallETool.cs b/ai/Squidex.AI/Implementation/OpenAI/DallETool.cs index 4a01edc..31871fe 100644 --- a/ai/Squidex.AI/Implementation/OpenAI/DallETool.cs +++ b/ai/Squidex.AI/Implementation/OpenAI/DallETool.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Betalgo.Ranul.OpenAI.Contracts.Requests.Image; using Betalgo.Ranul.OpenAI.Managers; using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels; using Microsoft.Extensions.Options; @@ -78,7 +79,7 @@ public async Task ExecuteAsync(ToolContext toolContext, throw new ChatException("Missing argument 'query'."); } - var request = new ImageCreateRequest(queryArg.ToString()) + var request = new CreateImageRequest(queryArg.ToString()) { Model = options.Model, Size = options.Size, @@ -98,9 +99,9 @@ public async Task ExecuteAsync(ToolContext toolContext, throw new ChatException($"Request failed with unknown error. HTTP {response.HttpStatusCode}."); } - var url = response.Results[0].Url; + var url = response.Data[0].Url; - if (options.DownloadImage) + if (options.DownloadImage && !string.IsNullOrWhiteSpace(url)) { url = await DownloadImageAsync(url, null, ct); } diff --git a/ai/Squidex.AI/Squidex.AI.csproj b/ai/Squidex.AI/Squidex.AI.csproj index 6156696..215ad50 100644 --- a/ai/Squidex.AI/Squidex.AI.csproj +++ b/ai/Squidex.AI/Squidex.AI.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,22 +12,20 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + diff --git a/assets/Benchmarks/Benchmarks.csproj b/assets/Benchmarks/Benchmarks.csproj index bf6898f..45a8ae9 100644 --- a/assets/Benchmarks/Benchmarks.csproj +++ b/assets/Benchmarks/Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 false Latest enable @@ -10,15 +10,13 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/assets/Squidex.Assets.Azure/AzureBlobAssetStore.cs b/assets/Squidex.Assets.Azure/AzureBlobAssetStore.cs index e8197e1..9bd7ec0 100644 --- a/assets/Squidex.Assets.Azure/AzureBlobAssetStore.cs +++ b/assets/Squidex.Assets.Azure/AzureBlobAssetStore.cs @@ -184,7 +184,7 @@ public async Task DeleteByPrefixAsync(string prefix, { var name = GetFileName(prefix, nameof(prefix)); - var items = blobContainer.GetBlobsAsync(prefix: name, cancellationToken: ct); + var items = blobContainer.GetBlobsAsync(BlobTraits.All, BlobStates.All, prefix: name, cancellationToken: ct); await foreach (var item in items.WithCancellation(ct)) { diff --git a/assets/Squidex.Assets.Azure/Squidex.Assets.Azure.csproj b/assets/Squidex.Assets.Azure/Squidex.Assets.Azure.csproj index 92c9774..fe1f9d2 100644 --- a/assets/Squidex.Assets.Azure/Squidex.Assets.Azure.csproj +++ b/assets/Squidex.Assets.Azure/Squidex.Assets.Azure.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.EntityFramework/Squidex.Assets.EntityFramework.csproj b/assets/Squidex.Assets.EntityFramework/Squidex.Assets.EntityFramework.csproj index fc95ae3..2d9de22 100644 --- a/assets/Squidex.Assets.EntityFramework/Squidex.Assets.EntityFramework.csproj +++ b/assets/Squidex.Assets.EntityFramework/Squidex.Assets.EntityFramework.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.FTP/Squidex.Assets.FTP.csproj b/assets/Squidex.Assets.FTP/Squidex.Assets.FTP.csproj index e81075a..eb7eec0 100644 --- a/assets/Squidex.Assets.FTP/Squidex.Assets.FTP.csproj +++ b/assets/Squidex.Assets.FTP/Squidex.Assets.FTP.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.GoogleCloud/Squidex.Assets.GoogleCloud.csproj b/assets/Squidex.Assets.GoogleCloud/Squidex.Assets.GoogleCloud.csproj index 099a35e..5c0f44a 100644 --- a/assets/Squidex.Assets.GoogleCloud/Squidex.Assets.GoogleCloud.csproj +++ b/assets/Squidex.Assets.GoogleCloud/Squidex.Assets.GoogleCloud.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.ImageMagick/Squidex.Assets.ImageMagick.csproj b/assets/Squidex.Assets.ImageMagick/Squidex.Assets.ImageMagick.csproj index 39b178f..091cf7d 100644 --- a/assets/Squidex.Assets.ImageMagick/Squidex.Assets.ImageMagick.csproj +++ b/assets/Squidex.Assets.ImageMagick/Squidex.Assets.ImageMagick.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable @@ -11,20 +11,18 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj b/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj index 42a05c4..f334613 100644 --- a/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj +++ b/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable @@ -11,20 +11,18 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - diff --git a/assets/Squidex.Assets.Mongo/Squidex.Assets.Mongo.csproj b/assets/Squidex.Assets.Mongo/Squidex.Assets.Mongo.csproj index 82cdc93..4bb7558 100644 --- a/assets/Squidex.Assets.Mongo/Squidex.Assets.Mongo.csproj +++ b/assets/Squidex.Assets.Mongo/Squidex.Assets.Mongo.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - diff --git a/assets/Squidex.Assets.ResizeService/Squidex.Assets.ResizeService.csproj b/assets/Squidex.Assets.ResizeService/Squidex.Assets.ResizeService.csproj index 66ce8c9..85eade5 100644 --- a/assets/Squidex.Assets.ResizeService/Squidex.Assets.ResizeService.csproj +++ b/assets/Squidex.Assets.ResizeService/Squidex.Assets.ResizeService.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -9,14 +9,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/assets/Squidex.Assets.S3/Squidex.Assets.S3.csproj b/assets/Squidex.Assets.S3/Squidex.Assets.S3.csproj index ec89c92..84c9e86 100644 --- a/assets/Squidex.Assets.S3/Squidex.Assets.S3.csproj +++ b/assets/Squidex.Assets.S3/Squidex.Assets.S3.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,17 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.Tests/Squidex.Assets.Tests.csproj b/assets/Squidex.Assets.Tests/Squidex.Assets.Tests.csproj index db12e42..24102ad 100644 --- a/assets/Squidex.Assets.Tests/Squidex.Assets.Tests.csproj +++ b/assets/Squidex.Assets.Tests/Squidex.Assets.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -10,25 +10,23 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + - - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/assets/Squidex.Assets.Tests/TusServerFixture.cs b/assets/Squidex.Assets.Tests/TusServerFixture.cs index 527ac6e..a988974 100644 --- a/assets/Squidex.Assets.Tests/TusServerFixture.cs +++ b/assets/Squidex.Assets.Tests/TusServerFixture.cs @@ -1,79 +1,83 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.AspNetCore.TestHost; -using MongoDB.Driver; -using MongoDB.Driver.GridFS; -using Squidex.Assets.TusAdapter; -using tusdotnet; -using tusdotnet.Interfaces; -using tusdotnet.Models; -using tusdotnet.Models.Configuration; - -namespace Squidex.Assets; - -public class TusServerFixture -{ - public static List Files { get; } = []; - - public TestServer TestServer { get; private set; } - - public HttpClient Client { get; private set; } - - public TusServerFixture() - { - TestServer = new TestServer(new WebHostBuilder() - .ConfigureLogging((context, builder) => - { - builder.ClearProviders(); - builder.ConfigureSemanticLog(context.Configuration); - }) - .ConfigureServices(services => - { - var mongoClient = new MongoClient("mongodb://localhost"); - var mongoDatabase = mongoClient.GetDatabase("TusTest"); - - var gridFSBucket = new GridFSBucket(mongoDatabase, new GridFSBucketOptions - { - BucketName = "fs", - }); - - services.AddSingleton(mongoDatabase); - services.AddInitializer(); - services.AddMongoAssetStore(c => gridFSBucket); - services.AddMongoAssetKeyValueStore(); - services.AddAssetTus(); - services.AddRouting(); - services.AddMvc(); - }) - .Configure(app => - { - app.UseTus(httpContext => new DefaultTusConfiguration - { - Store = httpContext.RequestServices.GetRequiredService(), - Events = new Events - { - OnFileCompleteAsync = async eventContext => - { - var file = (AssetTusFile)(await eventContext.GetFileAsync()); - - Files.Add(file); - }, - }, - UrlPath = "/files/middleware", - }); - - app.UseRouting(); - app.UseEndpoints(builder => - { - builder.MapControllers(); - }); - })); - - Client = TestServer.CreateClient(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.TestHost; +using MongoDB.Driver; +using MongoDB.Driver.GridFS; +using Squidex.Assets.TusAdapter; +using tusdotnet; +using tusdotnet.Interfaces; +using tusdotnet.Models; +using tusdotnet.Models.Configuration; + +namespace Squidex.Assets; + +public class TusServerFixture +{ + public static List Files { get; } = []; + + public TestServer TestServer { get; private set; } + + public HttpClient Client { get; private set; } + + public TusServerFixture() + { + var builder = WebApplication.CreateBuilder(); + builder.Logging.ClearProviders(); + builder.Logging.ConfigureSemanticLog(builder.Configuration); + + builder.Services.AddSingleton(() => + { + var mongoClient = new MongoClient("mongodb://localhost"); + var mongoDatabase = mongoClient.GetDatabase("TusTest"); + + return mongoDatabase; + }); + builder.Services.AddInitializer(); + builder.Services.AddMongoAssetStore(c => + { + var mongoDatabase = c.GetRequiredService(); + + var gridFSBucket = new GridFSBucket(mongoDatabase, new GridFSBucketOptions + { + BucketName = "fs", + }); + + return gridFSBucket; + }); + builder.Services.AddMongoAssetKeyValueStore(); + builder.Services.AddAssetTus(); + builder.Services.AddRouting(); + builder.Services.AddMvc(); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + app.UseTus(httpContext => new DefaultTusConfiguration + { + Store = httpContext.RequestServices.GetRequiredService(), + Events = new Events + { + OnFileCompleteAsync = async eventContext => + { + var file = (AssetTusFile)(await eventContext.GetFileAsync()); + + Files.Add(file); + }, + }, + UrlPath = "/files/middleware", + }); + + app.UseRouting(); + app.MapControllers(); + + app.Start(); + + TestServer = app.GetTestServer(); + + Client = TestServer.CreateClient(); + } +} diff --git a/assets/Squidex.Assets.TusAdapter/Squidex.Assets.TusAdapter.csproj b/assets/Squidex.Assets.TusAdapter/Squidex.Assets.TusAdapter.csproj index 5ee20b8..a1c7c02 100644 --- a/assets/Squidex.Assets.TusAdapter/Squidex.Assets.TusAdapter.csproj +++ b/assets/Squidex.Assets.TusAdapter/Squidex.Assets.TusAdapter.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,20 +12,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/assets/Squidex.Assets.TusClient/Squidex.Assets.TusClient.csproj b/assets/Squidex.Assets.TusClient/Squidex.Assets.TusClient.csproj index 140cba2..93cb5c0 100644 --- a/assets/Squidex.Assets.TusClient/Squidex.Assets.TusClient.csproj +++ b/assets/Squidex.Assets.TusClient/Squidex.Assets.TusClient.csproj @@ -1,10 +1,11 @@  - netstandard2.0;netcoreapp3.1;net6.0;net8.0 + netstandard2.0;netcoreapp3.1;net6.0;net8.0;net10.0 Latest enable enable + true @@ -12,15 +13,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/assets/Squidex.Assets/Remote/RemoteThumbnailGenerator.cs b/assets/Squidex.Assets/Remote/RemoteThumbnailGenerator.cs index 8e78b8e..feb0d4c 100644 --- a/assets/Squidex.Assets/Remote/RemoteThumbnailGenerator.cs +++ b/assets/Squidex.Assets/Remote/RemoteThumbnailGenerator.cs @@ -1,120 +1,121 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics.CodeAnalysis; -using System.Net.Http.Headers; -using System.Text; - -namespace Squidex.Assets.Remote; - -public sealed class RemoteThumbnailGenerator(IHttpClientFactory httpClientFactory, IAssetThumbnailGenerator inner) : AssetThumbnailGeneratorBase -{ - public override bool CanReadAndWrite(string mimeType) - { - return inner.CanReadAndWrite(mimeType); - } - - public override bool CanComputeBlurHash() - { - return inner.CanComputeBlurHash(); - } - - public override bool IsResizable(string mimeType, ResizeOptions options, [MaybeNullWhen(false)] out string? destinationMimeType) - { - return inner.IsResizable(mimeType, options, out destinationMimeType); - } - - protected override async Task ComputeBlurHashCoreAsync(Stream source, string mimeType, BlurOptions options, - CancellationToken ct = default) - { - using var httpClient = httpClientFactory.CreateClient("Resize"); - using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/blur?{BuildQueryString(options)}") - { - Content = new StreamContent(source), - }; - - httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); - - using var httpResponse = await httpClient.SendAsync(httpRequest, ct); - - httpResponse.EnsureSuccessStatusCode(); - - var result = await httpResponse.Content.ReadAsStringAsync(ct); - - if (string.IsNullOrWhiteSpace(result)) - { - result = null; - } - - return result; - } - - protected override async Task CreateThumbnailCoreAsync(Stream source, string mimeType, Stream destination, ResizeOptions options, - CancellationToken ct = default) - { - using var httpClient = httpClientFactory.CreateClient("Resize"); - using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/resize{BuildQueryString(options)}") - { - Content = new StreamContent(source), - }; - - httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); - - using var httpResonse = await httpClient.SendAsync(httpRequest, ct); - - httpResonse.EnsureSuccessStatusCode(); - - await httpResonse.Content.CopyToAsync(destination, ct); - } - - protected override async Task FixCoreAsync(Stream source, string mimeType, Stream destination, - CancellationToken ct = default) - { - using var httpClient = httpClientFactory.CreateClient("Resize"); - using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/orient") - { - Content = new StreamContent(source), - }; - - httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); - - var httpResponse = await httpClient.SendAsync(httpRequest, ct); - - httpResponse.EnsureSuccessStatusCode(); - - await httpResponse.Content.CopyToAsync(destination, ct); - } - - protected override Task GetImageInfoCoreAsync(Stream source, string mimeType, - CancellationToken ct = default) - { - return inner.GetImageInfoAsync(source, mimeType, ct); - } - - private static string BuildQueryString(IOptions options) - { - var sb = new StringBuilder(); - - foreach (var (key, value) in options.ToParameters()) - { - if (sb.Length > 0) - { - sb.Append('&'); - } - else - { - sb.Append('?'); - } - - sb.Append(key); - sb.Append('='); - sb.Append(Uri.EscapeDataString(value)); - } - - return sb.ToString(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; + +namespace Squidex.Assets.Remote; + +public sealed class RemoteThumbnailGenerator(IHttpClientFactory httpClientFactory, IAssetThumbnailGenerator inner) : AssetThumbnailGeneratorBase +{ + public override bool CanReadAndWrite(string mimeType) + { + return inner.CanReadAndWrite(mimeType); + } + + public override bool CanComputeBlurHash() + { + return inner.CanComputeBlurHash(); + } + + public override bool IsResizable(string mimeType, ResizeOptions options, [MaybeNullWhen(false)] out string? destinationMimeType) + { + return inner.IsResizable(mimeType, options, out destinationMimeType); + } + + protected override async Task ComputeBlurHashCoreAsync(Stream source, string mimeType, BlurOptions options, + CancellationToken ct = default) + { + using var httpClient = httpClientFactory.CreateClient("Resize"); + using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/blur?{BuildQueryString(options)}") + { + Content = new StreamContent(source), + }; + + httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); + + using var httpResponse = await httpClient.SendAsync(httpRequest, ct); + + httpResponse.EnsureSuccessStatusCode(); + + var result = await httpResponse.Content.ReadAsStringAsync(ct); + + if (string.IsNullOrWhiteSpace(result)) + { + result = null; + } + + return result; + } + + protected override async Task CreateThumbnailCoreAsync(Stream source, string mimeType, Stream destination, ResizeOptions options, + CancellationToken ct = default) + { + using var httpClient = httpClientFactory.CreateClient("Resize"); + using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/resize{BuildQueryString(options)}") + { + Content = new StreamContent(source), + }; + + httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); + + using var httpResonse = await httpClient.SendAsync(httpRequest, ct); + + httpResonse.EnsureSuccessStatusCode(); + + await httpResonse.Content.CopyToAsync(destination, ct); + } + + protected override async Task FixCoreAsync(Stream source, string mimeType, Stream destination, + CancellationToken ct = default) + { + using var httpClient = httpClientFactory.CreateClient("Resize"); + using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/orient") + { + Content = new StreamContent(source), + }; + + httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); + + var httpResponse = await httpClient.SendAsync(httpRequest, ct); + + httpResponse.EnsureSuccessStatusCode(); + + await httpResponse.Content.CopyToAsync(destination, ct); + } + + protected override Task GetImageInfoCoreAsync(Stream source, string mimeType, + CancellationToken ct = default) + { + return inner.GetImageInfoAsync(source, mimeType, ct); + } + + private static string BuildQueryString(IOptions options) + { + var sb = new StringBuilder(); + + foreach (var (key, value) in options.ToParameters()) + { + if (sb.Length > 0) + { + sb.Append('&'); + } + else + { + sb.Append('?'); + } + + sb.Append(key); + sb.Append('='); + sb.Append(Uri.EscapeDataString(value)); + } + + return sb.ToString(); + } +} diff --git a/assets/Squidex.Assets/Squidex.Assets.csproj b/assets/Squidex.Assets/Squidex.Assets.csproj index b163f71..ea09fb2 100644 --- a/assets/Squidex.Assets/Squidex.Assets.csproj +++ b/assets/Squidex.Assets/Squidex.Assets.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,21 +12,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/assets/TusTestServer/TusTestServer.csproj b/assets/TusTestServer/TusTestServer.csproj index ab2e921..9e2643d 100644 --- a/assets/TusTestServer/TusTestServer.csproj +++ b/assets/TusTestServer/TusTestServer.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -9,15 +9,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/assets/TusTestServer/Utils.cs b/assets/TusTestServer/Utils.cs index 0623e75..25e1486 100644 --- a/assets/TusTestServer/Utils.cs +++ b/assets/TusTestServer/Utils.cs @@ -1,58 +1,58 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Assets; -using Squidex.Assets.TusAdapter; -using Squidex.Assets.TusAdapter.Internal; -using tusdotnet; -using tusdotnet.Models; -using tusdotnet.Models.Configuration; - -namespace TusTestServer; - -public static class Utils -{ - public static void UseMyTus(this WebApplication app, string path) where T : IAssetStore - { - app.UseTus(httpContext => - { - var store = httpContext.RequestServices.GetRequiredService(); - - return new DefaultTusConfiguration - { - Store = new AssetTusStore( - store, - httpContext.RequestServices.GetRequiredService>()), - UrlPath = path, - Events = new Events - { - OnFileCompleteAsync = async eventContext => - { - var file = (IAssetFile)(await eventContext.GetFileAsync()); - - await using var fileStream = file.OpenRead(); - - var name = file.FileName; - - if (string.IsNullOrWhiteSpace(name)) - { - name = Guid.NewGuid().ToString(); - } - - Directory.CreateDirectory("uploads"); - - await using (var stream = new FileStream($"uploads/{name}", FileMode.Create)) - { - await fileStream.CopyToAsync(stream, eventContext.CancellationToken); - } - }, - }, - FileLockProvider = new AssetFileLockProvider(store), - }; - }); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Assets; +using Squidex.Assets.TusAdapter; +using Squidex.Assets.TusAdapter.Internal; +using tusdotnet; +using tusdotnet.Models; +using tusdotnet.Models.Configuration; + +namespace TusTestServer; + +public static class Utils +{ + public static void UseMyTus(this IApplicationBuilder app, string path) where T : IAssetStore + { + app.UseTus(httpContext => + { + var store = httpContext.RequestServices.GetRequiredService(); + + return new DefaultTusConfiguration + { + Store = new AssetTusStore( + store, + httpContext.RequestServices.GetRequiredService>()), + UrlPath = path, + Events = new Events + { + OnFileCompleteAsync = async eventContext => + { + var file = (IAssetFile)(await eventContext.GetFileAsync()); + + await using var fileStream = file.OpenRead(); + + var name = file.FileName; + + if (string.IsNullOrWhiteSpace(name)) + { + name = Guid.NewGuid().ToString(); + } + + Directory.CreateDirectory("uploads"); + + await using (var stream = new FileStream($"uploads/{name}", FileMode.Create)) + { + await fileStream.CopyToAsync(stream, eventContext.CancellationToken); + } + }, + }, + FileLockProvider = new AssetFileLockProvider(store), + }; + }); + } +} diff --git a/caching/Squidex.Caching.Tests/Squidex.Caching.Tests.csproj b/caching/Squidex.Caching.Tests/Squidex.Caching.Tests.csproj index 9036bb8..e1e4669 100644 --- a/caching/Squidex.Caching.Tests/Squidex.Caching.Tests.csproj +++ b/caching/Squidex.Caching.Tests/Squidex.Caching.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -10,20 +10,19 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/caching/Squidex.Caching/Squidex.Caching.csproj b/caching/Squidex.Caching/Squidex.Caching.csproj index 9a47dff..d773cd6 100644 --- a/caching/Squidex.Caching/Squidex.Caching.csproj +++ b/caching/Squidex.Caching/Squidex.Caching.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,15 +12,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/events/Squidex.Events.EntityFramework/Squidex.Events.EntityFramework.csproj b/events/Squidex.Events.EntityFramework/Squidex.Events.EntityFramework.csproj index 7255a9c..f4f9db3 100644 --- a/events/Squidex.Events.EntityFramework/Squidex.Events.EntityFramework.csproj +++ b/events/Squidex.Events.EntityFramework/Squidex.Events.EntityFramework.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,20 +12,19 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - diff --git a/events/Squidex.Events.GetEventStore/Squidex.Events.GetEventStore.csproj b/events/Squidex.Events.GetEventStore/Squidex.Events.GetEventStore.csproj index fa5f505..12e4ba5 100644 --- a/events/Squidex.Events.GetEventStore/Squidex.Events.GetEventStore.csproj +++ b/events/Squidex.Events.GetEventStore/Squidex.Events.GetEventStore.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,22 +12,18 @@ - - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/events/Squidex.Events.Mongo/MongoEventCommit.cs b/events/Squidex.Events.Mongo/MongoEventCommit.cs index 2bb3066..2c95d18 100644 --- a/events/Squidex.Events.Mongo/MongoEventCommit.cs +++ b/events/Squidex.Events.Mongo/MongoEventCommit.cs @@ -1,43 +1,44 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Squidex.Events.Mongo; - -public sealed class MongoEventCommit -{ - [BsonId] - [BsonElement] - [BsonRepresentation(BsonType.String)] - public Guid Id { get; set; } - - [BsonRequired] - [BsonElement(nameof(Timestamp))] - public BsonTimestamp Timestamp { get; set; } - - [BsonIgnoreIfDefault] - [BsonElement(nameof(GlobalPosition))] - public long GlobalPosition { get; set; } - - [BsonRequired] - [BsonElement(nameof(Events))] - public MongoEvent[] Events { get; set; } - - [BsonRequired] - [BsonElement(nameof(EventStreamOffset))] - public long EventStreamOffset { get; set; } - - [BsonRequired] - [BsonElement(nameof(EventsCount))] - public long EventsCount { get; set; } - - [BsonRequired] - [BsonElement(nameof(EventStream))] - public string EventStream { get; set; } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Events.Mongo; + +public sealed class MongoEventCommit +{ + [BsonId] + [BsonElement] + [BsonRepresentation(BsonType.String)] + [BsonGuidRepresentation(GuidRepresentation.CSharpLegacy)] + public Guid Id { get; set; } + + [BsonRequired] + [BsonElement(nameof(Timestamp))] + public BsonTimestamp Timestamp { get; set; } + + [BsonIgnoreIfDefault] + [BsonElement(nameof(GlobalPosition))] + public long GlobalPosition { get; set; } + + [BsonRequired] + [BsonElement(nameof(Events))] + public MongoEvent[] Events { get; set; } + + [BsonRequired] + [BsonElement(nameof(EventStreamOffset))] + public long EventStreamOffset { get; set; } + + [BsonRequired] + [BsonElement(nameof(EventsCount))] + public long EventsCount { get; set; } + + [BsonRequired] + [BsonElement(nameof(EventStream))] + public string EventStream { get; set; } +} diff --git a/events/Squidex.Events.Mongo/MongoGlobalPosition.cs b/events/Squidex.Events.Mongo/MongoGlobalPosition.cs index 45e6ab1..e8db91e 100644 --- a/events/Squidex.Events.Mongo/MongoGlobalPosition.cs +++ b/events/Squidex.Events.Mongo/MongoGlobalPosition.cs @@ -1,19 +1,24 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Events.Mongo; - -internal sealed class MongoGlobalPosition -{ - public long Id { get; set; } - - public long Position { get; set; } - - public DateTime? LockTaken { get; set; } - - public Guid LockOwner { get; set; } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Events.Mongo; + +internal sealed class MongoGlobalPosition +{ + public long Id { get; set; } + + public long Position { get; set; } + + public DateTime? LockTaken { get; set; } + + [BsonRepresentation(BsonType.Binary)] + [BsonGuidRepresentation(GuidRepresentation.CSharpLegacy)] + public Guid LockOwner { get; set; } +} diff --git a/events/Squidex.Events.Mongo/Squidex.Events.Mongo.csproj b/events/Squidex.Events.Mongo/Squidex.Events.Mongo.csproj index cc2472c..c5c7add 100644 --- a/events/Squidex.Events.Mongo/Squidex.Events.Mongo.csproj +++ b/events/Squidex.Events.Mongo/Squidex.Events.Mongo.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,12 +12,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/events/Squidex.Events.Tests/GetEventStoreFixture.cs b/events/Squidex.Events.Tests/GetEventStoreFixture.cs deleted file mode 100644 index 4a28f50..0000000 --- a/events/Squidex.Events.Tests/GetEventStoreFixture.cs +++ /dev/null @@ -1,57 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using EventStore.Client; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Squidex.Events.GetEventStore; -using Squidex.Hosting; -using Testcontainers.EventStoreDb; -using TestHelpers; - -namespace Squidex.Events; - -public sealed class GetEventStoreFixture : IAsyncLifetime -{ - private readonly EventStoreDbContainer eventStore = - new EventStoreDbBuilder() - .WithReuse(true) - .WithLabel("reuse-id", "eventstore-geteventstore") - .WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true") - .Build(); - - public IServiceProvider Services { get; private set; } - - public IEventStore Store => Services.GetRequiredService(); - - public async Task InitializeAsync() - { - await eventStore.StartAsync(); - - Services = new ServiceCollection() - .AddSingleton(_ => EventStoreClientSettings.Create(eventStore.GetConnectionString())) - .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")) - .AddGetEventStore(TestUtils.Configuration) - .Services - .BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await eventStore.StopAsync(); - } -} diff --git a/events/Squidex.Events.Tests/GetEventStoreTests.cs b/events/Squidex.Events.Tests/GetEventStoreTests.cs deleted file mode 100644 index 6fe0d2f..0000000 --- a/events/Squidex.Events.Tests/GetEventStoreTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Events; - -[Trait("Category", "Dependencies")] -public class GetEventStoreTests(GetEventStoreFixture fixture) - : EventStoreTests, IClassFixture -{ - protected override Task CreateSutAsync() - { - return Task.FromResult(fixture.Store); - } -} diff --git a/events/Squidex.Events.Tests/MongoEventStoreDocumentDbTests.cs b/events/Squidex.Events.Tests/MongoEventStoreDocumentDbTests.cs index ee2ab05..90482a2 100644 --- a/events/Squidex.Events.Tests/MongoEventStoreDocumentDbTests.cs +++ b/events/Squidex.Events.Tests/MongoEventStoreDocumentDbTests.cs @@ -35,7 +35,7 @@ public async Task InitializeAsync() ); var certPath = TestUtils.Configuration.GetValue("documentDb:keyFile")!; - var certFile = new X509Certificate2(certPath); + var certFile = X509CertificateLoader.LoadCertificateFromFile(certPath); settings.RetryWrites = false; settings.RetryReads = false; diff --git a/events/Squidex.Events.Tests/Squidex.Events.Tests.csproj b/events/Squidex.Events.Tests/Squidex.Events.Tests.csproj index bc4b1e9..13a9b1d 100644 --- a/events/Squidex.Events.Tests/Squidex.Events.Tests.csproj +++ b/events/Squidex.Events.Tests/Squidex.Events.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -15,26 +15,23 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + - - - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -53,7 +50,6 @@ - diff --git a/events/Squidex.Events/Squidex.Events.csproj b/events/Squidex.Events/Squidex.Events.csproj index 62954fa..0fbfec5 100644 --- a/events/Squidex.Events/Squidex.Events.csproj +++ b/events/Squidex.Events/Squidex.Events.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,13 +12,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/flows/Squidex.Flows.EntityFramework/Squidex.Flows.EntityFramework.csproj b/flows/Squidex.Flows.EntityFramework/Squidex.Flows.EntityFramework.csproj index 527ecad..2c6a66c 100644 --- a/flows/Squidex.Flows.EntityFramework/Squidex.Flows.EntityFramework.csproj +++ b/flows/Squidex.Flows.EntityFramework/Squidex.Flows.EntityFramework.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,21 +12,19 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - diff --git a/flows/Squidex.Flows.Mongo/MongoFlowStateEntity.cs b/flows/Squidex.Flows.Mongo/MongoFlowStateEntity.cs index ce38e34..ad3f6d6 100644 --- a/flows/Squidex.Flows.Mongo/MongoFlowStateEntity.cs +++ b/flows/Squidex.Flows.Mongo/MongoFlowStateEntity.cs @@ -1,37 +1,38 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Squidex.Flows.Mongo; - -public sealed class MongoFlowStateEntity -{ - [BsonRepresentation(BsonType.String)] - public Guid Id { get; set; } - - [BsonElement("o")] - public string OwnerId { get; set; } - - [BsonElement("c")] - [BsonRepresentation(BsonType.String)] - public DateTimeOffset Created { get; set; } - - [BsonElement("d")] - public string DefinitionId { get; set; } - - [BsonElement("s")] - public string State { get; set; } - - [BsonElement("p")] - public int SchedulePartition { get; set; } - - [BsonElement("ts")] - [BsonRepresentation(BsonType.String)] - public DateTimeOffset? DueTime { get; set; } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Flows.Mongo; + +public sealed class MongoFlowStateEntity +{ + [BsonRepresentation(BsonType.String)] + [BsonGuidRepresentation(GuidRepresentation.CSharpLegacy)] + public Guid Id { get; set; } + + [BsonElement("o")] + public string OwnerId { get; set; } + + [BsonElement("c")] + [BsonRepresentation(BsonType.String)] + public DateTimeOffset Created { get; set; } + + [BsonElement("d")] + public string DefinitionId { get; set; } + + [BsonElement("s")] + public string State { get; set; } + + [BsonElement("p")] + public int SchedulePartition { get; set; } + + [BsonElement("ts")] + [BsonRepresentation(BsonType.String)] + public DateTimeOffset? DueTime { get; set; } +} diff --git a/flows/Squidex.Flows.Mongo/Squidex.Flows.Mongo.csproj b/flows/Squidex.Flows.Mongo/Squidex.Flows.Mongo.csproj index 105e66b..ac3fb16 100644 --- a/flows/Squidex.Flows.Mongo/Squidex.Flows.Mongo.csproj +++ b/flows/Squidex.Flows.Mongo/Squidex.Flows.Mongo.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,21 +12,19 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - diff --git a/flows/Squidex.Flows.Tests/EFFlowsFixture.cs b/flows/Squidex.Flows.Tests/EFFlowsFixture.cs index b030ca4..c75f78a 100644 --- a/flows/Squidex.Flows.Tests/EFFlowsFixture.cs +++ b/flows/Squidex.Flows.Tests/EFFlowsFixture.cs @@ -1,71 +1,71 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using PhenX.EntityFrameworkCore.BulkInsert.Extensions; -using PhenX.EntityFrameworkCore.BulkInsert.Options; -using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; -using Squidex.Flows.EntityFramework; -using TestHelpers; -using TestHelpers.EntityFramework; - -#pragma warning disable MA0048 // File name must match type name - -namespace Squidex.Flows; - -public sealed class EFFlowsDbContext(DbContextOptions options) : DbContext(options) -{ - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseBulkInsertPostgreSql(); - base.OnConfiguring(optionsBuilder); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.UseFlows(); - modelBuilder.UseCronJobs(); - base.OnModelCreating(modelBuilder); - } -} - -public sealed class EFFlowsFixture() : PostgresFixture("flows-postgres") -{ - protected override void AddServices(IServiceCollection services) - { - services.AddFlows(TestUtils.Configuration) - .AddEntityFrameworkStore(); - - services.AddCronJobs(TestUtils.Configuration) - .AddEntityFrameworkStore(); - - services.AddSingleton(); - } -} - -public sealed class BulkInserter : IDbFlowsBulkInserter -{ - public Task BulkUpsertAsync(DbContext dbContext, IEnumerable entities, - CancellationToken ct = default) where T : class - { - return dbContext.ExecuteBulkInsertAsync( - entities, - null!, - new OnConflictOptions - { - Update = e => e, - }, - ct); - } -} - -[CollectionDefinition(Name)] -public class EFFlowsCollection : ICollectionFixture -{ - public const string Name = "flows-postgres"; -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using PhenX.EntityFrameworkCore.BulkInsert.Extensions; +using PhenX.EntityFrameworkCore.BulkInsert.Options; +using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; +using Squidex.Flows.EntityFramework; +using TestHelpers; +using TestHelpers.EntityFramework; + +#pragma warning disable MA0048 // File name must match type name + +namespace Squidex.Flows; + +public sealed class EFFlowsDbContext(DbContextOptions options) : DbContext(options) +{ + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseBulkInsertPostgreSql(); + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.UseFlows(); + modelBuilder.UseCronJobs(); + base.OnModelCreating(modelBuilder); + } +} + +public sealed class EFFlowsFixture() : PostgresFixture("flows-postgres") +{ + protected override void AddServices(IServiceCollection services) + { + services.AddFlows(TestUtils.Configuration) + .AddEntityFrameworkStore(); + + services.AddCronJobs(TestUtils.Configuration) + .AddEntityFrameworkStore(); + + services.AddSingleton(); + } +} + +public sealed class BulkInserter : IDbFlowsBulkInserter +{ + public Task BulkUpsertAsync(DbContext dbContext, IEnumerable entities, + CancellationToken ct = default) where T : class + { + return dbContext.ExecuteBulkInsertAsync( + entities, + null!, + new OnConflictOptions + { + Update = (lhs, rhs) => lhs, + }, + ct); + } +} + +[CollectionDefinition(Name)] +public class EFFlowsCollection : ICollectionFixture +{ + public const string Name = "flows-postgres"; +} diff --git a/flows/Squidex.Flows.Tests/Squidex.Flows.Tests.csproj b/flows/Squidex.Flows.Tests/Squidex.Flows.Tests.csproj index 21a6498..b948aca 100644 --- a/flows/Squidex.Flows.Tests/Squidex.Flows.Tests.csproj +++ b/flows/Squidex.Flows.Tests/Squidex.Flows.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -10,26 +10,23 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + - - - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/flows/Squidex.Flows/Internal/Execution/DefaultFlowExecutor.cs b/flows/Squidex.Flows/Internal/Execution/DefaultFlowExecutor.cs index e2a8676..d95dd8c 100644 --- a/flows/Squidex.Flows/Internal/Execution/DefaultFlowExecutor.cs +++ b/flows/Squidex.Flows/Internal/Execution/DefaultFlowExecutor.cs @@ -1,421 +1,418 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.ComponentModel.DataAnnotations; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NodaTime; -using NodaTime.Extensions; - -namespace Squidex.Flows.Internal.Execution; - -public sealed class DefaultFlowExecutor( - IServiceProvider serviceProvider, - IEnumerable middlewares, - IEnumerable> callbacks, - IFlowErrorPolicy errorPolicy, - IFlowExpressionEngine expressionEngine, - IOptions flowOptions, - ILogger> log) - : IFlowExecutor where TContext : FlowContext -{ - private readonly PipelineDelegate pipeline = BuildPipeline(middlewares); - - private TimeSpan DefaultTimeout => flowOptions.Value.DefaultTimeout; - - public IClock Clock { get; set; } = SystemClock.Instance; - - public async Task ValidateAsync(FlowDefinition definition, AddError addError, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(nameof(definition)); - ArgumentNullException.ThrowIfNull(nameof(addError)); - - if (definition.Steps.Count == 0) - { - addError(string.Empty, ValidationErrorType.NoSteps); - return; - } - - if (definition.InitialStepId == null || - definition.InitialStepId == default || - !definition.Steps.ContainsKey(definition.InitialStepId.Value)) - { - addError(string.Empty, ValidationErrorType.NoStartStep); - } - - var context = new FlowValidationContext(serviceProvider, definition); - - foreach (var (stepId, stepDefinition) in definition.Steps) - { - if (stepId == default) - { - addError(string.Empty, ValidationErrorType.InvalidStepId); - } - - if (stepDefinition.NextStepId != null && - stepDefinition.NextStepId != default && - !definition.Steps.ContainsKey(stepDefinition.NextStepId.Value)) - { - addError($"Steps.{stepId}", ValidationErrorType.InvalidNextStepId); - } - - if (stepDefinition.Step is null) - { - addError($"Steps.{stepId}.Step", ValidationErrorType.InvalidStep); - return; - } - - await ValidateCoreAsync(stepDefinition.Step, context, (path, type, message) => - { - addError($"Steps.{stepId}.Step.{path}", type, message); - }, ct); - } - } - - public async Task ValidateAsync(FlowStep step, AddError addError, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(nameof(step)); - ArgumentNullException.ThrowIfNull(nameof(addError)); - - await ValidateCoreAsync(step, new FlowValidationContext(serviceProvider, null), addError, ct); - } - - private static async Task ValidateCoreAsync(FlowStep step, FlowValidationContext validationContext, AddError addError, - CancellationToken ct) - { - var context = new ValidationContext(step); - var errors = new List(); - - Validator.TryValidateObject(step, context, errors, true); - - foreach (var error in errors) - { - if (string.IsNullOrWhiteSpace(error.ErrorMessage)) - { - continue; - } - - foreach (var member in error.MemberNames) - { - addError(member, ValidationErrorType.InvalidProperty, error.ErrorMessage); - } - } - - await step.ValidateAsync(validationContext, (path, message) => - { - addError(path, ValidationErrorType.InvalidProperty, message); - }, ct); - } - - public FlowExecutionState CreateState(CreateFlowInstanceRequest request) - { - ArgumentNullException.ThrowIfNull(nameof(request.Definition)); - ArgumentNullException.ThrowIfNull(nameof(request.Context)); - ArgumentNullException.ThrowIfNull(nameof(request.ScheduleKey)); - ArgumentException.ThrowIfNullOrWhiteSpace(nameof(request.OwnerId)); - ArgumentException.ThrowIfNullOrWhiteSpace(nameof(request.DefinitionId)); - ArgumentException.ThrowIfNullOrWhiteSpace(nameof(request.Description)); - ArgumentException.ThrowIfNullOrWhiteSpace(nameof(request.ScheduleKey)); - - if (request.Definition.Steps.Count == 0) - { - throw new InvalidOperationException($"Flow definition has no steps."); - } - - if (request.Definition.InitialStepId == null) - { - throw new InvalidOperationException($"Flow definition has initial step."); - } - - if (!request.Definition.Steps.ContainsKey(request.Definition.InitialStepId.Value)) - { - throw new InvalidOperationException($"Flow definition has no step with ID '{request.Definition.InitialStepId}'."); - } - - var scheduleKey = request.ScheduleKey ?? string.Empty; - var schedulePartition = flowOptions.Value.GetPartition(scheduleKey); - - var state = new FlowExecutionState - { - Created = Clock.GetCurrentInstant(), - Context = request.Context, - Definition = request.Definition, - DefinitionId = request.DefinitionId, - Description = request.Description ?? string.Empty, - Expires = Clock.GetCurrentInstant().Plus(flowOptions.Value.Expiration.ToDuration()), - InstanceId = Guid.NewGuid(), - NextRun = Clock.GetCurrentInstant(), - NextStepId = request.Definition.InitialStepId, - OwnerId = request.OwnerId, - ScheduleKey = scheduleKey, - SchedulePartition = schedulePartition, - }; - - return state; - } - - public async Task SimulateAsync(FlowExecutionState state, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(nameof(state)); - - while (true) - { - if (state.Status is FlowExecutionStatus.Completed or FlowExecutionStatus.Failed) - { - break; - } - - await ExecuteCoreAsync(state, true, default, ct); - } - } - - public async Task ExecuteAsync(FlowExecutionState state, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(nameof(state)); - - try - { - while (true) - { - if (state.Status is FlowExecutionStatus.Completed or FlowExecutionStatus.Failed) - { - break; - } - - var now = Clock.GetCurrentInstant(); - if (state.NextRun > now) - { - break; - } - - await ExecuteCoreAsync(state, false, DefaultTimeout, ct); - } - } - catch - { - // This method should never fail, when used properly. - state.Failed(Clock.GetCurrentInstant()); - throw; - } - } - - private async Task ExecuteCoreAsync(FlowExecutionState state, bool simulate, TimeSpan timeout, - CancellationToken ct) - { - var definition = state.Definition; - - var stepId = state.NextStepId ?? Guid.Empty; - if (stepId == default) - { - throw new InvalidOperationException("Flow has not next step."); - } - - if (!definition.Steps.TryGetValue(stepId, out var stepDefinition)) - { - throw new InvalidOperationException($"Cannot find step with ID '{definition.InitialStepId}'."); - } - - if (state.Status == FlowExecutionStatus.Scheduled) - { - state.Status = FlowExecutionStatus.Running; - } - - var stepState = state.Step(stepId); - var stepAttempt = stepState.NextAttempt(Clock.GetCurrentInstant()); - - void Log(string message, string? dump) - { - lock (stepAttempt.Log) - { - stepAttempt.Log.Add(new FlowExecutionStepLogEntry(Clock.GetCurrentInstant(), message, dump)); - } - } - - var executionContext = new FlowExecutionContext( - expressionEngine, - stepDefinition.Step, - state.Context, - serviceProvider, - Log, - simulate); - - using (var cts = CreateCancellationTokenSource(timeout, ct)) - { - // Detect circular references to other steps. - if (stepState.Status is FlowExecutionStatus.Completed or FlowExecutionStatus.Failed) - { - throw new InvalidOperationException("Flow step has already been completed."); - } - - try - { - // Ensure that we also catch errors in the preparation. - await PrepareAsync(executionContext, stepDefinition.Step, stepState, cts.Token); - - stepState.Status = FlowExecutionStatus.Running; - - var result = await pipeline(executionContext, ct) ?? - throw new InvalidOperationException("Step does not return a valid result."); - - HandleSuccess(state, stepDefinition, stepState, result); - } - catch (Exception ex) - { - HandleError(state, stepId, stepDefinition, stepState, stepAttempt, ex); - } - finally - { - stepAttempt.Completed = Clock.GetCurrentInstant(); - } - - await HandleCallbacksAsync(state, ct); - } - } - - private async Task HandleCallbacksAsync(FlowExecutionState state, - CancellationToken ct) - { - foreach (var callback in callbacks) - { - try - { - await callback.OnUpdateAsync(state, ct); - } - catch (Exception ex) - { - log.LogError(ex, "Failed to execute {callback}.", callback); - } - } - } - - private void HandleError( - FlowExecutionState state, - Guid stepId, - FlowStepDefinition stepDefinition, - FlowExecutionStepState stepState, - FlowExecutionStepAttempt stepAttempt, - Exception ex) - { - // Ensure to take the time after the step execution and preparation. - var now = Clock.GetCurrentInstant(); - - if (!stepState.IsPrepared) - { - state.Failed(now); - stepState.Status = FlowExecutionStatus.Failed; - } - else if (stepDefinition.IgnoreError) - { - var nextId = state.GetNextStep(stepDefinition, default); - if (nextId != null) - { - state.Next(nextId.Value, Instant.MinValue); - } - else - { - state.Complete(now); - } - } - else - { - var nextAttempt = errorPolicy.ShouldRetry(state, stepState, stepDefinition.Step, now); - if (nextAttempt > now) - { - state.Next(stepId, nextAttempt.Value); - } - else - { - state.Failed(now); - stepState.Status = FlowExecutionStatus.Failed; - } - } - - if (flowOptions.Value.IsSafeException?.Invoke(ex) == true) - { - stepAttempt.Error = ex.Message; - } - } - - private void HandleSuccess( - FlowExecutionState state, - FlowStepDefinition stepDefinition, - FlowExecutionStepState stepState, - FlowStepResult result) - { - // Ensure to take the time after the step execution and preparation. - var now = Clock.GetCurrentInstant(); - - // This step has been successful (we do not support loops). - stepState.Status = FlowExecutionStatus.Completed; - - if (result.Type == FlowStepResultType.Next) - { - var nextId = state.GetNextStep(stepDefinition, result.StepId); - if (nextId != null) - { - state.Next(nextId.Value, result.Scheduled); - return; - } - } - - state.Complete(now); - } - - private static CancellationTokenSource CreateCancellationTokenSource(TimeSpan timeout, CancellationToken ct) - { - var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - - if (timeout > TimeSpan.Zero) - { - cts.CancelAfter(timeout); - } - - return cts; - } - - private static async Task PrepareAsync( - FlowExecutionContext executionContext, - FlowStep step, - FlowExecutionStepState stepState, - CancellationToken ct) - { - if (stepState.IsPrepared) - { - return; - } - - await step.EvaluateExpressionsAsync(executionContext); - await step.PrepareAsync(executionContext, ct); - stepState.IsPrepared = true; - } - - private static PipelineDelegate BuildPipeline(IEnumerable middlewares) - { - return new PipelineDelegate((executionContext, ct) => - { - NextStepDelegate next = () => - { - return executionContext.Step.ExecuteAsync(executionContext, ct); - }; - - foreach (var middleware in middlewares.Reverse()) - { - var currentNext = next; - next = () => - { - return middleware.InvokeAsync(executionContext, currentNext, ct); - }; - } - - return next(); - }); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NodaTime; +using NodaTime.Extensions; + +namespace Squidex.Flows.Internal.Execution; + +public sealed class DefaultFlowExecutor( + IServiceProvider serviceProvider, + IEnumerable middlewares, + IEnumerable> callbacks, + IFlowErrorPolicy errorPolicy, + IFlowExpressionEngine expressionEngine, + IOptions flowOptions, + ILogger> log) + : IFlowExecutor where TContext : FlowContext +{ + private readonly PipelineDelegate pipeline = BuildPipeline(middlewares); + + private TimeSpan DefaultTimeout => flowOptions.Value.DefaultTimeout; + + public IClock Clock { get; set; } = SystemClock.Instance; + + public async Task ValidateAsync(FlowDefinition definition, AddError addError, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(definition); + ArgumentNullException.ThrowIfNull(addError); + + if (definition.Steps.Count == 0) + { + addError(string.Empty, ValidationErrorType.NoSteps); + return; + } + + if (definition.InitialStepId == null || + definition.InitialStepId == default || + !definition.Steps.ContainsKey(definition.InitialStepId.Value)) + { + addError(string.Empty, ValidationErrorType.NoStartStep); + } + + var context = new FlowValidationContext(serviceProvider, definition); + + foreach (var (stepId, stepDefinition) in definition.Steps) + { + if (stepId == default) + { + addError(string.Empty, ValidationErrorType.InvalidStepId); + } + + if (stepDefinition.NextStepId != null && + stepDefinition.NextStepId != default && + !definition.Steps.ContainsKey(stepDefinition.NextStepId.Value)) + { + addError($"Steps.{stepId}", ValidationErrorType.InvalidNextStepId); + } + + if (stepDefinition.Step is null) + { + addError($"Steps.{stepId}.Step", ValidationErrorType.InvalidStep); + return; + } + + await ValidateCoreAsync(stepDefinition.Step, context, (path, type, message) => + { + addError($"Steps.{stepId}.Step.{path}", type, message); + }, ct); + } + } + + public async Task ValidateAsync(FlowStep step, AddError addError, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(step); + ArgumentNullException.ThrowIfNull(addError); + + await ValidateCoreAsync(step, new FlowValidationContext(serviceProvider, null), addError, ct); + } + + private static async Task ValidateCoreAsync(FlowStep step, FlowValidationContext validationContext, AddError addError, + CancellationToken ct) + { + var context = new ValidationContext(step); + var errors = new List(); + + Validator.TryValidateObject(step, context, errors, true); + + foreach (var error in errors) + { + if (string.IsNullOrWhiteSpace(error.ErrorMessage)) + { + continue; + } + + foreach (var member in error.MemberNames) + { + addError(member, ValidationErrorType.InvalidProperty, error.ErrorMessage); + } + } + + await step.ValidateAsync(validationContext, (path, message) => + { + addError(path, ValidationErrorType.InvalidProperty, message); + }, ct); + } + + public FlowExecutionState CreateState(CreateFlowInstanceRequest request) + { + ArgumentNullException.ThrowIfNull(request.Definition); + ArgumentNullException.ThrowIfNull(request.Context); + ArgumentException.ThrowIfNullOrWhiteSpace(request.OwnerId); + ArgumentException.ThrowIfNullOrWhiteSpace(request.DefinitionId); + + if (request.Definition.Steps.Count == 0) + { + throw new InvalidOperationException($"Flow definition has no steps."); + } + + if (request.Definition.InitialStepId == null) + { + throw new InvalidOperationException($"Flow definition has initial step."); + } + + if (!request.Definition.Steps.ContainsKey(request.Definition.InitialStepId.Value)) + { + throw new InvalidOperationException($"Flow definition has no step with ID '{request.Definition.InitialStepId}'."); + } + + var scheduleKey = request.ScheduleKey ?? string.Empty; + var schedulePartition = flowOptions.Value.GetPartition(scheduleKey); + + var state = new FlowExecutionState + { + Created = Clock.GetCurrentInstant(), + Context = request.Context, + Definition = request.Definition, + DefinitionId = request.DefinitionId, + Description = request.Description ?? string.Empty, + Expires = Clock.GetCurrentInstant().Plus(flowOptions.Value.Expiration.ToDuration()), + InstanceId = Guid.NewGuid(), + NextRun = Clock.GetCurrentInstant(), + NextStepId = request.Definition.InitialStepId, + OwnerId = request.OwnerId, + ScheduleKey = scheduleKey, + SchedulePartition = schedulePartition, + }; + + return state; + } + + public async Task SimulateAsync(FlowExecutionState state, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(state); + + while (true) + { + if (state.Status is FlowExecutionStatus.Completed or FlowExecutionStatus.Failed) + { + break; + } + + await ExecuteCoreAsync(state, true, default, ct); + } + } + + public async Task ExecuteAsync(FlowExecutionState state, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(state); + + try + { + while (true) + { + if (state.Status is FlowExecutionStatus.Completed or FlowExecutionStatus.Failed) + { + break; + } + + var now = Clock.GetCurrentInstant(); + if (state.NextRun > now) + { + break; + } + + await ExecuteCoreAsync(state, false, DefaultTimeout, ct); + } + } + catch + { + // This method should never fail, when used properly. + state.Failed(Clock.GetCurrentInstant()); + throw; + } + } + + private async Task ExecuteCoreAsync(FlowExecutionState state, bool simulate, TimeSpan timeout, + CancellationToken ct) + { + var definition = state.Definition; + + var stepId = state.NextStepId ?? Guid.Empty; + if (stepId == default) + { + throw new InvalidOperationException("Flow has not next step."); + } + + if (!definition.Steps.TryGetValue(stepId, out var stepDefinition)) + { + throw new InvalidOperationException($"Cannot find step with ID '{definition.InitialStepId}'."); + } + + if (state.Status == FlowExecutionStatus.Scheduled) + { + state.Status = FlowExecutionStatus.Running; + } + + var stepState = state.Step(stepId); + var stepAttempt = stepState.NextAttempt(Clock.GetCurrentInstant()); + + void Log(string message, string? dump) + { + lock (stepAttempt.Log) + { + stepAttempt.Log.Add(new FlowExecutionStepLogEntry(Clock.GetCurrentInstant(), message, dump)); + } + } + + var executionContext = new FlowExecutionContext( + expressionEngine, + stepDefinition.Step, + state.Context, + serviceProvider, + Log, + simulate); + + using (var cts = CreateCancellationTokenSource(timeout, ct)) + { + // Detect circular references to other steps. + if (stepState.Status is FlowExecutionStatus.Completed or FlowExecutionStatus.Failed) + { + throw new InvalidOperationException("Flow step has already been completed."); + } + + try + { + // Ensure that we also catch errors in the preparation. + await PrepareAsync(executionContext, stepDefinition.Step, stepState, cts.Token); + + stepState.Status = FlowExecutionStatus.Running; + + var result = await pipeline(executionContext, ct) ?? + throw new InvalidOperationException("Step does not return a valid result."); + + HandleSuccess(state, stepDefinition, stepState, result); + } + catch (Exception ex) + { + HandleError(state, stepId, stepDefinition, stepState, stepAttempt, ex); + } + finally + { + stepAttempt.Completed = Clock.GetCurrentInstant(); + } + + await HandleCallbacksAsync(state, ct); + } + } + + private async Task HandleCallbacksAsync(FlowExecutionState state, + CancellationToken ct) + { + foreach (var callback in callbacks) + { + try + { + await callback.OnUpdateAsync(state, ct); + } + catch (Exception ex) + { + log.LogError(ex, "Failed to execute {callback}.", callback); + } + } + } + + private void HandleError( + FlowExecutionState state, + Guid stepId, + FlowStepDefinition stepDefinition, + FlowExecutionStepState stepState, + FlowExecutionStepAttempt stepAttempt, + Exception ex) + { + // Ensure to take the time after the step execution and preparation. + var now = Clock.GetCurrentInstant(); + + if (!stepState.IsPrepared) + { + state.Failed(now); + stepState.Status = FlowExecutionStatus.Failed; + } + else if (stepDefinition.IgnoreError) + { + var nextId = state.GetNextStep(stepDefinition, default); + if (nextId != null) + { + state.Next(nextId.Value, Instant.MinValue); + } + else + { + state.Complete(now); + } + } + else + { + var nextAttempt = errorPolicy.ShouldRetry(state, stepState, stepDefinition.Step, now); + if (nextAttempt > now) + { + state.Next(stepId, nextAttempt.Value); + } + else + { + state.Failed(now); + stepState.Status = FlowExecutionStatus.Failed; + } + } + + if (flowOptions.Value.IsSafeException?.Invoke(ex) == true) + { + stepAttempt.Error = ex.Message; + } + } + + private void HandleSuccess( + FlowExecutionState state, + FlowStepDefinition stepDefinition, + FlowExecutionStepState stepState, + FlowStepResult result) + { + // Ensure to take the time after the step execution and preparation. + var now = Clock.GetCurrentInstant(); + + // This step has been successful (we do not support loops). + stepState.Status = FlowExecutionStatus.Completed; + + if (result.Type == FlowStepResultType.Next) + { + var nextId = state.GetNextStep(stepDefinition, result.StepId); + if (nextId != null) + { + state.Next(nextId.Value, result.Scheduled); + return; + } + } + + state.Complete(now); + } + + private static CancellationTokenSource CreateCancellationTokenSource(TimeSpan timeout, CancellationToken ct) + { + var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + + if (timeout > TimeSpan.Zero) + { + cts.CancelAfter(timeout); + } + + return cts; + } + + private static async Task PrepareAsync( + FlowExecutionContext executionContext, + FlowStep step, + FlowExecutionStepState stepState, + CancellationToken ct) + { + if (stepState.IsPrepared) + { + return; + } + + await step.EvaluateExpressionsAsync(executionContext); + await step.PrepareAsync(executionContext, ct); + stepState.IsPrepared = true; + } + + private static PipelineDelegate BuildPipeline(IEnumerable middlewares) + { + return new PipelineDelegate((executionContext, ct) => + { + NextStepDelegate next = () => + { + return executionContext.Step.ExecuteAsync(executionContext, ct); + }; + + foreach (var middleware in middlewares.Reverse()) + { + var currentNext = next; + next = () => + { + return middleware.InvokeAsync(executionContext, currentNext, ct); + }; + } + + return next(); + }); + } +} diff --git a/flows/Squidex.Flows/Squidex.Flows.csproj b/flows/Squidex.Flows/Squidex.Flows.csproj index 9788d33..11d094f 100644 --- a/flows/Squidex.Flows/Squidex.Flows.csproj +++ b/flows/Squidex.Flows/Squidex.Flows.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,22 +12,20 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - diff --git a/global.json b/global.json index 3f39c45..6a15c38 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.416" + "version": "10.0.201" } } \ No newline at end of file diff --git a/hosting/Squidex.Hosting.Abstractions/Squidex.Hosting.Abstractions.csproj b/hosting/Squidex.Hosting.Abstractions/Squidex.Hosting.Abstractions.csproj index 351314c..6ffd627 100644 --- a/hosting/Squidex.Hosting.Abstractions/Squidex.Hosting.Abstractions.csproj +++ b/hosting/Squidex.Hosting.Abstractions/Squidex.Hosting.Abstractions.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable Squidex.Hosting @@ -13,16 +13,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/hosting/Squidex.Hosting.TestRunner/Squidex.Hosting.TestRunner.csproj b/hosting/Squidex.Hosting.TestRunner/Squidex.Hosting.TestRunner.csproj index 632f540..76bea1f 100644 --- a/hosting/Squidex.Hosting.TestRunner/Squidex.Hosting.TestRunner.csproj +++ b/hosting/Squidex.Hosting.TestRunner/Squidex.Hosting.TestRunner.csproj @@ -1,14 +1,14 @@  - net8.0 + net10.0 Squidex.Hosting false enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/hosting/Squidex.Hosting.Tests/Squidex.Hosting.Tests.csproj b/hosting/Squidex.Hosting.Tests/Squidex.Hosting.Tests.csproj index a5d3895..d51a4bc 100644 --- a/hosting/Squidex.Hosting.Tests/Squidex.Hosting.Tests.csproj +++ b/hosting/Squidex.Hosting.Tests/Squidex.Hosting.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 Latest enable enable @@ -10,22 +10,22 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/hosting/Squidex.Hosting/Squidex.Hosting.csproj b/hosting/Squidex.Hosting/Squidex.Hosting.csproj index 5f30a2f..39d1728 100644 --- a/hosting/Squidex.Hosting/Squidex.Hosting.csproj +++ b/hosting/Squidex.Hosting/Squidex.Hosting.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -16,12 +16,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/hosting/Squidex.Hosting/Web/ConfigureForwardedHeaders.cs b/hosting/Squidex.Hosting/Web/ConfigureForwardedHeaders.cs index 0be2862..44d8094 100644 --- a/hosting/Squidex.Hosting/Web/ConfigureForwardedHeaders.cs +++ b/hosting/Squidex.Hosting/Web/ConfigureForwardedHeaders.cs @@ -1,44 +1,43 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.Options; - -namespace Squidex.Hosting.Web; - -public sealed class ConfigureForwardedHeaders(IOptions urlOptions, IUrlGenerator urlGenerator) : IConfigureOptions -{ - private readonly UrlOptions urlOptions = urlOptions.Value; - - public void Configure(ForwardedHeadersOptions options) - { - options.AllowedHosts = - [ - urlGenerator.BuildHost().ToString(), - ]; - - options.ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; - options.ForwardLimit = null; - options.RequireHeaderSymmetry = false; - - options.KnownNetworks.Clear(); - options.KnownProxies.Clear(); - - if (urlOptions.KnownProxies != null) - { - foreach (var proxy in urlOptions.KnownProxies) - { - if (IPAddress.TryParse(proxy, out var address)) - { - options.KnownProxies.Add(address); - } - } - } - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.Options; + +namespace Squidex.Hosting.Web; + +public sealed class ConfigureForwardedHeaders(IOptions urlOptions, IUrlGenerator urlGenerator) : IConfigureOptions +{ + private readonly UrlOptions urlOptions = urlOptions.Value; + + public void Configure(ForwardedHeadersOptions options) + { + options.AllowedHosts = + [ + urlGenerator.BuildHost().ToString(), + ]; + + options.ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; + options.ForwardLimit = null; + options.RequireHeaderSymmetry = false; + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); + + if (urlOptions.KnownProxies != null) + { + foreach (var proxy in urlOptions.KnownProxies) + { + if (IPAddress.TryParse(proxy, out var address)) + { + options.KnownProxies.Add(address); + } + } + } + } +} diff --git a/log/Squidex.Log.Tests/Squidex.Log.Tests.csproj b/log/Squidex.Log.Tests/Squidex.Log.Tests.csproj index 67c5b3e..4568325 100644 --- a/log/Squidex.Log.Tests/Squidex.Log.Tests.csproj +++ b/log/Squidex.Log.Tests/Squidex.Log.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 Latest enable enable @@ -10,20 +10,20 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/log/Squidex.Log/FileChannel.cs b/log/Squidex.Log/FileChannel.cs index b00bc16..d81e1ce 100644 --- a/log/Squidex.Log/FileChannel.cs +++ b/log/Squidex.Log/FileChannel.cs @@ -1,47 +1,47 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Log.Internal; - -namespace Squidex.Log; - -public sealed class FileChannel : IDisposable, ILogChannel -{ - private readonly FileLogProcessor processor; - private readonly object lockObject = new object(); - private volatile bool isInitialized; - - public FileChannel(string path) - { - Guard.NotNullOrEmpty(path, nameof(path)); - - processor = new FileLogProcessor(path); - } - - public void Dispose() - { - processor.Dispose(); - } - - public void Log(SemanticLogLevel logLevel, string message) - { - if (!isInitialized) - { - lock (lockObject) - { - if (!isInitialized) - { - processor.Initialize(); - - isInitialized = true; - } - } - } - - processor.EnqueueMessage(new LogMessageEntry { Message = message }); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Log.Internal; + +namespace Squidex.Log; + +public sealed class FileChannel : IDisposable, ILogChannel +{ + private readonly FileLogProcessor processor; + private readonly Lock lockObject = new Lock(); + private volatile bool isInitialized; + + public FileChannel(string path) + { + Guard.NotNullOrEmpty(path, nameof(path)); + + processor = new FileLogProcessor(path); + } + + public void Dispose() + { + processor.Dispose(); + } + + public void Log(SemanticLogLevel logLevel, string message) + { + if (!isInitialized) + { + lock (lockObject) + { + if (!isInitialized) + { + processor.Initialize(); + + isInitialized = true; + } + } + } + + processor.EnqueueMessage(new LogMessageEntry { Message = message }); + } +} diff --git a/log/Squidex.Log/Squidex.Log.csproj b/log/Squidex.Log/Squidex.Log.csproj index 5ba9c85..5189624 100644 --- a/log/Squidex.Log/Squidex.Log.csproj +++ b/log/Squidex.Log/Squidex.Log.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,14 +12,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/messaging/Squidex.Messaging.All/Squidex.Messaging.All.csproj b/messaging/Squidex.Messaging.All/Squidex.Messaging.All.csproj index 7827b8d..51eecab 100644 --- a/messaging/Squidex.Messaging.All/Squidex.Messaging.All.csproj +++ b/messaging/Squidex.Messaging.All/Squidex.Messaging.All.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,13 +12,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/messaging/Squidex.Messaging.EntityFramework/Squidex.Messaging.EntityFramework.csproj b/messaging/Squidex.Messaging.EntityFramework/Squidex.Messaging.EntityFramework.csproj index 642c0a0..10deaaa 100644 --- a/messaging/Squidex.Messaging.EntityFramework/Squidex.Messaging.EntityFramework.csproj +++ b/messaging/Squidex.Messaging.EntityFramework/Squidex.Messaging.EntityFramework.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,16 +12,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/messaging/Squidex.Messaging.GoogleCloud/Squidex.Messaging.GoogleCloud.csproj b/messaging/Squidex.Messaging.GoogleCloud/Squidex.Messaging.GoogleCloud.csproj index cbda526..7d50453 100644 --- a/messaging/Squidex.Messaging.GoogleCloud/Squidex.Messaging.GoogleCloud.csproj +++ b/messaging/Squidex.Messaging.GoogleCloud/Squidex.Messaging.GoogleCloud.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,13 +12,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/messaging/Squidex.Messaging.Kafka/Squidex.Messaging.Kafka.csproj b/messaging/Squidex.Messaging.Kafka/Squidex.Messaging.Kafka.csproj index 9ecb00e..1f587c9 100644 --- a/messaging/Squidex.Messaging.Kafka/Squidex.Messaging.Kafka.csproj +++ b/messaging/Squidex.Messaging.Kafka/Squidex.Messaging.Kafka.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,13 +12,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/messaging/Squidex.Messaging.Mongo/Squidex.Messaging.Mongo.csproj b/messaging/Squidex.Messaging.Mongo/Squidex.Messaging.Mongo.csproj index ae22c81..eec8049 100644 --- a/messaging/Squidex.Messaging.Mongo/Squidex.Messaging.Mongo.csproj +++ b/messaging/Squidex.Messaging.Mongo/Squidex.Messaging.Mongo.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,16 +12,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/messaging/Squidex.Messaging.RabbitMq/Squidex.Messaging.RabbitMq.csproj b/messaging/Squidex.Messaging.RabbitMq/Squidex.Messaging.RabbitMq.csproj index 5b58fb0..e90360a 100644 --- a/messaging/Squidex.Messaging.RabbitMq/Squidex.Messaging.RabbitMq.csproj +++ b/messaging/Squidex.Messaging.RabbitMq/Squidex.Messaging.RabbitMq.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,16 +12,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/messaging/Squidex.Messaging.Redis/Squidex.Messaging.Redis.csproj b/messaging/Squidex.Messaging.Redis/Squidex.Messaging.Redis.csproj index 89aa07a..adf9bbd 100644 --- a/messaging/Squidex.Messaging.Redis/Squidex.Messaging.Redis.csproj +++ b/messaging/Squidex.Messaging.Redis/Squidex.Messaging.Redis.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,17 +12,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/messaging/Squidex.Messaging.Subscriptions/Squidex.Messaging.Subscriptions.csproj b/messaging/Squidex.Messaging.Subscriptions/Squidex.Messaging.Subscriptions.csproj index 2e694a5..4d43bc4 100644 --- a/messaging/Squidex.Messaging.Subscriptions/Squidex.Messaging.Subscriptions.csproj +++ b/messaging/Squidex.Messaging.Subscriptions/Squidex.Messaging.Subscriptions.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,18 +12,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/messaging/Squidex.Messaging.Tests/KafkaFixture.cs b/messaging/Squidex.Messaging.Tests/KafkaFixture.cs index fd27504..61fd5cf 100644 --- a/messaging/Squidex.Messaging.Tests/KafkaFixture.cs +++ b/messaging/Squidex.Messaging.Tests/KafkaFixture.cs @@ -1,41 +1,41 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics; -using Confluent.Kafka; -using Confluent.Kafka.Admin; -using Testcontainers.Kafka; - -namespace Squidex.Messaging; - -public class KafkaFixture : IAsyncLifetime -{ - public KafkaContainer Kafka { get; } = - new KafkaBuilder() - .WithReuse(Debugger.IsAttached) - .WithLabel("reuse-id", "messaging-kafka") - .Build(); - - public async Task InitializeAsync() - { - await Kafka.StartAsync(); - - using var adminClient = - new AdminClientBuilder( - new AdminClientConfig { BootstrapServers = Kafka.GetBootstrapAddress() }) - .Build(); - - await adminClient.CreateTopicsAsync([ - new TopicSpecification { Name = "dev" }, - ]); - } - - public async Task DisposeAsync() - { - await Kafka.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; +using Confluent.Kafka; +using Confluent.Kafka.Admin; +using Testcontainers.Kafka; + +namespace Squidex.Messaging; + +public class KafkaFixture : IAsyncLifetime +{ + public KafkaContainer Kafka { get; } = + new KafkaBuilder("confluentinc/cp-kafka:7.5.12") + .WithReuse(Debugger.IsAttached) + .WithLabel("reuse-id", "messaging-kafka") + .Build(); + + public async Task InitializeAsync() + { + await Kafka.StartAsync(); + + using var adminClient = + new AdminClientBuilder( + new AdminClientConfig { BootstrapServers = Kafka.GetBootstrapAddress() }) + .Build(); + + await adminClient.CreateTopicsAsync([ + new TopicSpecification { Name = "dev" }, + ]); + } + + public async Task DisposeAsync() + { + await Kafka.StopAsync(); + } +} diff --git a/messaging/Squidex.Messaging.Tests/RabbitMqFixture.cs b/messaging/Squidex.Messaging.Tests/RabbitMqFixture.cs index e501f76..be1450d 100644 --- a/messaging/Squidex.Messaging.Tests/RabbitMqFixture.cs +++ b/messaging/Squidex.Messaging.Tests/RabbitMqFixture.cs @@ -1,30 +1,30 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics; -using Testcontainers.RabbitMq; - -namespace Squidex.Messaging; - -public class RabbitMqFixture : IAsyncLifetime -{ - public RabbitMqContainer RabbitMq { get; } = - new RabbitMqBuilder() - .WithReuse(Debugger.IsAttached) - .WithLabel("reuse-id", "messaging-rabbit") - .Build(); - - public async Task InitializeAsync() - { - await RabbitMq.StartAsync(); - } - - public async Task DisposeAsync() - { - await RabbitMq.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; +using Testcontainers.RabbitMq; + +namespace Squidex.Messaging; + +public class RabbitMqFixture : IAsyncLifetime +{ + public RabbitMqContainer RabbitMq { get; } = + new RabbitMqBuilder("rabbitmq:3.11") + .WithReuse(Debugger.IsAttached) + .WithLabel("reuse-id", "messaging-rabbit") + .Build(); + + public async Task InitializeAsync() + { + await RabbitMq.StartAsync(); + } + + public async Task DisposeAsync() + { + await RabbitMq.StopAsync(); + } +} diff --git a/messaging/Squidex.Messaging.Tests/RedisFixture.cs b/messaging/Squidex.Messaging.Tests/RedisFixture.cs index 6e58c82..b1538a1 100644 --- a/messaging/Squidex.Messaging.Tests/RedisFixture.cs +++ b/messaging/Squidex.Messaging.Tests/RedisFixture.cs @@ -1,37 +1,37 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics; -using StackExchange.Redis; -using Testcontainers.Redis; - -#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one - -namespace Squidex.Messaging; - -public class RedisFixture : IAsyncLifetime -{ - private readonly RedisContainer redis = - new RedisBuilder() - .WithReuse(Debugger.IsAttached) - .WithLabel("reuse-id", "messaging-redis") - .Build(); - - public ConnectionMultiplexer Connection { get; set; } - - public async Task InitializeAsync() - { - await redis.StartAsync(); - - Connection = await ConnectionMultiplexer.ConnectAsync(redis.GetConnectionString()); - } - - public async Task DisposeAsync() - { - await redis.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; +using StackExchange.Redis; +using Testcontainers.Redis; + +#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one + +namespace Squidex.Messaging; + +public class RedisFixture : IAsyncLifetime +{ + private readonly RedisContainer redis = + new RedisBuilder("redis:7.0") + .WithReuse(Debugger.IsAttached) + .WithLabel("reuse-id", "messaging-redis") + .Build(); + + public ConnectionMultiplexer Connection { get; set; } + + public async Task InitializeAsync() + { + await redis.StartAsync(); + + Connection = await ConnectionMultiplexer.ConnectAsync(redis.GetConnectionString()); + } + + public async Task DisposeAsync() + { + await redis.StopAsync(); + } +} diff --git a/messaging/Squidex.Messaging.Tests/Squidex.Messaging.Tests.csproj b/messaging/Squidex.Messaging.Tests/Squidex.Messaging.Tests.csproj index 497cc83..da7a064 100644 --- a/messaging/Squidex.Messaging.Tests/Squidex.Messaging.Tests.csproj +++ b/messaging/Squidex.Messaging.Tests/Squidex.Messaging.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable false Latest @@ -10,29 +10,27 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + - - - - - + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/messaging/Squidex.Messaging.Tests/SubscriptionServiceTests.cs b/messaging/Squidex.Messaging.Tests/SubscriptionServiceTests.cs index fd0c6f2..d3b8122 100644 --- a/messaging/Squidex.Messaging.Tests/SubscriptionServiceTests.cs +++ b/messaging/Squidex.Messaging.Tests/SubscriptionServiceTests.cs @@ -1,246 +1,245 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Reactive.Linq; -using Squidex.Messaging.Implementation; -using Squidex.Messaging.Internal; -using Squidex.Messaging.Subscriptions; -using TestHelpers; - -#pragma warning disable MA0040 // Flow the cancellation token - -namespace Squidex.Messaging; - -[Collection(MongoMessagingCollection.Name)] -public class SubscriptionServiceTests(MongoMessagingFixture fixture) -{ - private readonly string groupName = $"group-{Guid.NewGuid()}"; - private readonly string key = $"key-{Guid.NewGuid()}"; - - [Fact] - public async Task Should_subscribe_hot() - { - await using var app = await CreateSutAsync(); - - await app.Sut.SubscribeAsync(key); - - Assert.True(await app.Sut.HasSubscriptionsAsync(key)); - } - - [Fact] - public async Task Should_subscribe_not_twice() - { - await using var app = await CreateSutAsync(); - - (await app.Sut.SubscribeAsync(key)).Subscribe(); - - Assert.True(await app.Sut.HasSubscriptionsAsync(key)); - } - - [Fact] - public async Task Should_unsubscribe() - { - await using var app = await CreateSutAsync(); - - using ((await app.Sut.SubscribeAsync(key)).Subscribe()) - { - Assert.True(await WaitForSubscriptions(app.Sut, true)); - } - - Assert.False(await WaitForSubscriptions(app.Sut, false)); - } - - [Fact] - public async Task Should_load_existing_subscriptions_from_db() - { - await using var app = await CreateSutAsync(); - - using ((await app.Sut.SubscribeAsync(key)).Subscribe()) - { - await using var app2 = await CreateSutAsync(); - - Assert.True(await app.Sut.HasSubscriptionsAsync(key)); - } - } - - [Fact] - public async Task Should_synchronize_subscriptions() - { - await using var app1 = await CreateSutAsync(); - await using var app2 = await CreateSutAsync(); - - using ((await app1.Sut.SubscribeAsync(key)).Subscribe()) - { - Assert.True(await WaitForSubscriptions(app1.Sut, true)); - Assert.True(await WaitForSubscriptions(app2.Sut, true)); - } - - await Task.Delay(200); - - Assert.False(await WaitForSubscriptions(app1.Sut, false)); - Assert.False(await WaitForSubscriptions(app2.Sut, false)); - } - - [Fact] - public async Task Should_subscribe() - { - await using var app = await CreateSutAsync(); - - using ((await app.Sut.SubscribeAsync(key)).Subscribe()) - { - Assert.True(await app.Sut.HasSubscriptionsAsync(key)); - } - - await Task.Delay(200); - - Assert.False(await app.Sut.HasSubscriptionsAsync(key)); - } - - [Fact] - public async Task Should_publish_to_self() - { - await using var app = await CreateSutAsync(); - - var received = Completion(); - - using ((await app.Sut.SubscribeAsync(key)).Subscribe(x => - { - received.TrySetResult(x); - })) - { - while (received.Task.Status is not TaskStatus.Faulted and not TaskStatus.RanToCompletion) - { - await app.Sut.PublishAsync(key, new TestMessage(Guid.NewGuid(), 42)); - await Task.Delay(100); - } - } - - Assert.Equal(42, (await received.Task as TestMessage)?.Value); - } - - [Fact] - public async Task Should_publish_to_other_instances() - { - await using var app1 = await CreateSutAsync(); - await using var app2 = await CreateSutAsync(); - - var received = Completion(); - - using ((await app1.Sut.SubscribeAsync(key)).Subscribe(x => - { - received.TrySetResult(x); - })) - { - while (received.Task.Status is not TaskStatus.Faulted and not TaskStatus.RanToCompletion) - { - await app2.Sut.PublishAsync(key, new TestMessage(Guid.NewGuid(), 42)); - await Task.Delay(100); - } - } - - Assert.Equal(42, (await received.Task as TestMessage)?.Value); - } - - [Fact] - public async Task Should_publish_to_other_instances_with_wrapper() - { - await using var app1 = await CreateSutAsync(); - await using var app2 = await CreateSutAsync(); - - var received = Completion(); - - using ((await app1.Sut.SubscribeAsync(key)).Subscribe(x => - { - received.TrySetResult(x); - })) - { - while (received.Task.Status is not TaskStatus.Faulted and not TaskStatus.RanToCompletion) - { - await app2.Sut.PublishAsync(key, new Wrapper()); - await Task.Delay(100); - } - } - - Assert.Equal(42, (await received.Task as TestMessage)?.Value); - } - - private sealed class Wrapper : IPayloadWrapper - { - public object Message { get; } = new TestMessage(Guid.NewGuid(), 42); - - public ValueTask CreatePayloadAsync() - { - return new ValueTask(Message); - } - } - - private async Task WaitForSubscriptions(ISubscriptionService sut, bool expected) - { - using var cts = new CancellationTokenSource(30_000); - - while (!cts.IsCancellationRequested) - { - if ((await sut.HasSubscriptionsAsync(key)) == expected) - { - return expected; - } - - await Task.Delay(100); - } - - return !expected; - } - - private static TaskCompletionSource Completion() - { - var completion = new TaskCompletionSource(); - var cancelled = new CancellationTokenSource(30_000); - - var registration = cancelled.Token.Register(() => - { - completion.TrySetCanceled(); - }); -#pragma warning disable MA0134 // Observe result of async calls - completion.Task.ContinueWith(x => - { - cancelled.Dispose(); - }); -#pragma warning restore MA0134 // Observe result of async calls - - return completion; - } - - private Task> CreateSutAsync() - { - var serviceProvider = - new ServiceCollection() - .AddLogging(options => - { - options.AddDebug(); - options.AddConsole(); - }) - .AddSingleton(fixture.MongoDatabase) - .AddReplicatedCache() - .AddMessaging() - .Configure(options => - { - options.DataCacheDuration = TimeSpan.Zero; - }) - .Configure(options => - { - options.GroupName = groupName; - }) - .AddSubscriptions() - .AddMongoDataStore(TestUtils.Configuration) - .AddMongoTransport(TestUtils.Configuration) - .Services - .AddSingleton() - .BuildServiceProvider(); - - return serviceProvider.CreateAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Messaging.Implementation; +using Squidex.Messaging.Internal; +using Squidex.Messaging.Subscriptions; +using TestHelpers; + +#pragma warning disable MA0040 // Flow the cancellation token + +namespace Squidex.Messaging; + +[Collection(MongoMessagingCollection.Name)] +public class SubscriptionServiceTests(MongoMessagingFixture fixture) +{ + private readonly string groupName = $"group-{Guid.NewGuid()}"; + private readonly string key = $"key-{Guid.NewGuid()}"; + + [Fact] + public async Task Should_subscribe_hot() + { + await using var app = await CreateSutAsync(); + + await app.Sut.SubscribeAsync(key); + + Assert.True(await app.Sut.HasSubscriptionsAsync(key)); + } + + [Fact] + public async Task Should_subscribe_not_twice() + { + await using var app = await CreateSutAsync(); + + (await app.Sut.SubscribeAsync(key)).Subscribe(); + + Assert.True(await app.Sut.HasSubscriptionsAsync(key)); + } + + [Fact] + public async Task Should_unsubscribe() + { + await using var app = await CreateSutAsync(); + + using ((await app.Sut.SubscribeAsync(key)).Subscribe()) + { + Assert.True(await WaitForSubscriptions(app.Sut, true)); + } + + Assert.False(await WaitForSubscriptions(app.Sut, false)); + } + + [Fact] + public async Task Should_load_existing_subscriptions_from_db() + { + await using var app = await CreateSutAsync(); + + using ((await app.Sut.SubscribeAsync(key)).Subscribe()) + { + await using var app2 = await CreateSutAsync(); + + Assert.True(await app.Sut.HasSubscriptionsAsync(key)); + } + } + + [Fact] + public async Task Should_synchronize_subscriptions() + { + await using var app1 = await CreateSutAsync(); + await using var app2 = await CreateSutAsync(); + + using ((await app1.Sut.SubscribeAsync(key)).Subscribe()) + { + Assert.True(await WaitForSubscriptions(app1.Sut, true)); + Assert.True(await WaitForSubscriptions(app2.Sut, true)); + } + + await Task.Delay(200); + + Assert.False(await WaitForSubscriptions(app1.Sut, false)); + Assert.False(await WaitForSubscriptions(app2.Sut, false)); + } + + [Fact] + public async Task Should_subscribe() + { + await using var app = await CreateSutAsync(); + + using ((await app.Sut.SubscribeAsync(key)).Subscribe()) + { + Assert.True(await app.Sut.HasSubscriptionsAsync(key)); + } + + await Task.Delay(200); + + Assert.False(await app.Sut.HasSubscriptionsAsync(key)); + } + + [Fact] + public async Task Should_publish_to_self() + { + await using var app = await CreateSutAsync(); + + var received = Completion(); + + using ((await app.Sut.SubscribeAsync(key)).Subscribe(x => + { + received.TrySetResult(x); + })) + { + while (received.Task.Status is not TaskStatus.Faulted and not TaskStatus.RanToCompletion) + { + await app.Sut.PublishAsync(key, new TestMessage(Guid.NewGuid(), 42)); + await Task.Delay(100); + } + } + + Assert.Equal(42, (await received.Task as TestMessage)?.Value); + } + + [Fact] + public async Task Should_publish_to_other_instances() + { + await using var app1 = await CreateSutAsync(); + await using var app2 = await CreateSutAsync(); + + var received = Completion(); + + using ((await app1.Sut.SubscribeAsync(key)).Subscribe(x => + { + received.TrySetResult(x); + })) + { + while (received.Task.Status is not TaskStatus.Faulted and not TaskStatus.RanToCompletion) + { + await app2.Sut.PublishAsync(key, new TestMessage(Guid.NewGuid(), 42)); + await Task.Delay(100); + } + } + + Assert.Equal(42, (await received.Task as TestMessage)?.Value); + } + + [Fact] + public async Task Should_publish_to_other_instances_with_wrapper() + { + await using var app1 = await CreateSutAsync(); + await using var app2 = await CreateSutAsync(); + + var received = Completion(); + + using ((await app1.Sut.SubscribeAsync(key)).Subscribe(x => + { + received.TrySetResult(x); + })) + { + while (received.Task.Status is not TaskStatus.Faulted and not TaskStatus.RanToCompletion) + { + await app2.Sut.PublishAsync(key, new Wrapper()); + await Task.Delay(100); + } + } + + Assert.Equal(42, (await received.Task as TestMessage)?.Value); + } + + private sealed class Wrapper : IPayloadWrapper + { + public object Message { get; } = new TestMessage(Guid.NewGuid(), 42); + + public ValueTask CreatePayloadAsync() + { + return new ValueTask(Message); + } + } + + private async Task WaitForSubscriptions(ISubscriptionService sut, bool expected) + { + using var cts = new CancellationTokenSource(30_000); + + while (!cts.IsCancellationRequested) + { + if ((await sut.HasSubscriptionsAsync(key)) == expected) + { + return expected; + } + + await Task.Delay(100); + } + + return !expected; + } + + private static TaskCompletionSource Completion() + { + var completion = new TaskCompletionSource(); + var cancelled = new CancellationTokenSource(30_000); + + var registration = cancelled.Token.Register(() => + { + completion.TrySetCanceled(); + }); +#pragma warning disable MA0134 // Observe result of async calls + completion.Task.ContinueWith(x => + { + cancelled.Dispose(); + }); +#pragma warning restore MA0134 // Observe result of async calls + + return completion; + } + + private Task> CreateSutAsync() + { + var serviceProvider = + new ServiceCollection() + .AddLogging(options => + { + options.AddDebug(); + options.AddConsole(); + }) + .AddSingleton(fixture.MongoDatabase) + .AddReplicatedCache() + .AddMessaging() + .Configure(options => + { + options.DataCacheDuration = TimeSpan.Zero; + }) + .Configure(options => + { + options.GroupName = groupName; + }) + .AddSubscriptions() + .AddMongoDataStore(TestUtils.Configuration) + .AddMongoTransport(TestUtils.Configuration) + .Services + .AddSingleton() + .BuildServiceProvider(); + + return serviceProvider.CreateAsync(); + } +} diff --git a/messaging/Squidex.Messaging/Squidex.Messaging.csproj b/messaging/Squidex.Messaging/Squidex.Messaging.csproj index 0b58602..08c97de 100644 --- a/messaging/Squidex.Messaging/Squidex.Messaging.csproj +++ b/messaging/Squidex.Messaging/Squidex.Messaging.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,19 +12,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/text/Squidex.Text.Tests/Squidex.Text.Tests.csproj b/text/Squidex.Text.Tests/Squidex.Text.Tests.csproj index 2bbaab9..bb1835f 100644 --- a/text/Squidex.Text.Tests/Squidex.Text.Tests.csproj +++ b/text/Squidex.Text.Tests/Squidex.Text.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -11,25 +11,23 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/text/Squidex.Text/Squidex.Text.csproj b/text/Squidex.Text/Squidex.Text.csproj index 8eac5c2..811b41c 100644 --- a/text/Squidex.Text/Squidex.Text.csproj +++ b/text/Squidex.Text/Squidex.Text.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -12,28 +12,27 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - diff --git a/text/Squidex.Text/Translations/MissingKeys.cs b/text/Squidex.Text/Translations/MissingKeys.cs index ef44c1f..2830d02 100644 --- a/text/Squidex.Text/Translations/MissingKeys.cs +++ b/text/Squidex.Text/Translations/MissingKeys.cs @@ -1,40 +1,40 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Text.Translations; - -public sealed class MissingKeys -{ - private const string MissingFileName = "__missing.txt"; - private readonly object lockObject = new object(); - private readonly HashSet missingTranslations; - - public MissingKeys() - { - if (File.Exists(MissingFileName)) - { - var missing = File.ReadAllLines(MissingFileName); - - missingTranslations = new HashSet(missing); - } - else - { - missingTranslations = []; - } - } - - public void Log(string key) - { - lock (lockObject) - { - if (!missingTranslations.Add(key)) - { - File.AppendAllLines(MissingFileName, [key]); - } - } - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Text.Translations; + +public sealed class MissingKeys +{ + private const string MissingFileName = "__missing.txt"; + private readonly Lock lockObject = new Lock(); + private readonly HashSet missingTranslations; + + public MissingKeys() + { + if (File.Exists(MissingFileName)) + { + var missing = File.ReadAllLines(MissingFileName); + + missingTranslations = new HashSet(missing); + } + else + { + missingTranslations = []; + } + } + + public void Log(string key) + { + lock (lockObject) + { + if (!missingTranslations.Add(key)) + { + File.AppendAllLines(MissingFileName, [key]); + } + } + } +} diff --git a/utils/TestHelpers/EntityFramework/MariaDbFixture.cs b/utils/TestHelpers/EntityFramework/MariaDbFixture.cs index 1b54e04..251a9e7 100644 --- a/utils/TestHelpers/EntityFramework/MariaDbFixture.cs +++ b/utils/TestHelpers/EntityFramework/MariaDbFixture.cs @@ -1,64 +1,64 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Hosting; -using Testcontainers.MariaDb; - -namespace TestHelpers.EntityFramework; - -public abstract class MariaDbFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext -{ - public MariaDbContainer MariaDb { get; } = - new MariaDbBuilder() - .WithReuse(true) - .WithLabel("reuse-id", reuseId) - .WithCommand("--log-bin-trust-function-creators=1", "--local-infile=1") - .Build(); - - public IServiceProvider Services { get; private set; } - - public IDbContextFactory DbContextFactory - => Services.GetRequiredService>(); - - public async Task InitializeAsync() - { - await MariaDb.StartAsync(); - - var connectionString = $"{MariaDb.GetConnectionString()};AllowLoadLocalInfile=true;MaxPoolSize=1000"; - - var serviceCollection = - new ServiceCollection() - .AddSingleton>() - .AddDbContextFactory(b => - { - b.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); - }); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await MariaDb.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Hosting; +using Testcontainers.MariaDb; + +namespace TestHelpers.EntityFramework; + +public abstract class MariaDbFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext +{ + public MariaDbContainer MariaDb { get; } = + new MariaDbBuilder("mariadb:10.10") + .WithReuse(true) + .WithLabel("reuse-id", reuseId) + .WithCommand("--log-bin-trust-function-creators=1", "--local-infile=1") + .Build(); + + public IServiceProvider Services { get; private set; } + + public IDbContextFactory DbContextFactory + => Services.GetRequiredService>(); + + public async Task InitializeAsync() + { + await MariaDb.StartAsync(); + + var connectionString = $"{MariaDb.GetConnectionString()};AllowLoadLocalInfile=true;MaxPoolSize=1000"; + + var serviceCollection = + new ServiceCollection() + .AddSingleton>() + .AddDbContextFactory(b => + { + b.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + }); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await MariaDb.StopAsync(); + } +} diff --git a/utils/TestHelpers/EntityFramework/MySqlFixture.cs b/utils/TestHelpers/EntityFramework/MySqlFixture.cs index fde6059..7961472 100644 --- a/utils/TestHelpers/EntityFramework/MySqlFixture.cs +++ b/utils/TestHelpers/EntityFramework/MySqlFixture.cs @@ -1,64 +1,64 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Hosting; -using Testcontainers.MySql; - -namespace TestHelpers.EntityFramework; - -public abstract class MySqlFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext -{ - public MySqlContainer Mysql { get; } = - new MySqlBuilder() - .WithReuse(true) - .WithLabel("reuse-id", reuseId) - .WithCommand("--log-bin-trust-function-creators=1", "--local-infile=1") - .Build(); - - public IServiceProvider Services { get; private set; } - - public IDbContextFactory DbContextFactory - => Services.GetRequiredService>(); - - public async Task InitializeAsync() - { - await Mysql.StartAsync(); - - var connectionString = $"{Mysql.GetConnectionString()};AllowLoadLocalInfile=true;MaxPoolSize=1000"; - - var serviceCollection = - new ServiceCollection() - .AddSingleton>() - .AddDbContextFactory(b => - { - b.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); - }); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await Mysql.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Hosting; +using Testcontainers.MySql; + +namespace TestHelpers.EntityFramework; + +public abstract class MySqlFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext +{ + public MySqlContainer Mysql { get; } = + new MySqlBuilder("mysql:8.0") + .WithReuse(true) + .WithLabel("reuse-id", reuseId) + .WithCommand("--log-bin-trust-function-creators=1", "--local-infile=1") + .Build(); + + public IServiceProvider Services { get; private set; } + + public IDbContextFactory DbContextFactory + => Services.GetRequiredService>(); + + public async Task InitializeAsync() + { + await Mysql.StartAsync(); + + var connectionString = $"{Mysql.GetConnectionString()};AllowLoadLocalInfile=true;MaxPoolSize=1000"; + + var serviceCollection = + new ServiceCollection() + .AddSingleton>() + .AddDbContextFactory(b => + { + b.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + }); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await Mysql.StopAsync(); + } +} diff --git a/utils/TestHelpers/EntityFramework/PostgresFixture.cs b/utils/TestHelpers/EntityFramework/PostgresFixture.cs index b80073d..a41817f 100644 --- a/utils/TestHelpers/EntityFramework/PostgresFixture.cs +++ b/utils/TestHelpers/EntityFramework/PostgresFixture.cs @@ -1,62 +1,61 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Hosting; -using Testcontainers.PostgreSql; - -namespace TestHelpers.EntityFramework; - -public abstract class PostgresFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext -{ - public PostgreSqlContainer PostgreSql { get; } = - new PostgreSqlBuilder() - .WithImage("postgis/postgis") - .WithReuse(true) - .WithLabel("reuse-id", reuseId) - .Build(); - - public IServiceProvider Services { get; private set; } - - public IDbContextFactory DbContextFactory - => Services.GetRequiredService>(); - - public async Task InitializeAsync() - { - await PostgreSql.StartAsync(); - - var serviceCollection = - new ServiceCollection() - .AddSingleton>() - .AddDbContextFactory(b => - { - b.UseNpgsql(PostgreSql.GetConnectionString()); - }); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await PostgreSql.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Hosting; +using Testcontainers.PostgreSql; + +namespace TestHelpers.EntityFramework; + +public abstract class PostgresFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext +{ + public PostgreSqlContainer PostgreSql { get; } = + new PostgreSqlBuilder("postgis/postgis") + .WithReuse(true) + .WithLabel("reuse-id", reuseId) + .Build(); + + public IServiceProvider Services { get; private set; } + + public IDbContextFactory DbContextFactory + => Services.GetRequiredService>(); + + public async Task InitializeAsync() + { + await PostgreSql.StartAsync(); + + var serviceCollection = + new ServiceCollection() + .AddSingleton>() + .AddDbContextFactory(b => + { + b.UseNpgsql(PostgreSql.GetConnectionString()); + }); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await PostgreSql.StopAsync(); + } +} diff --git a/utils/TestHelpers/EntityFramework/SqlServerFixture.cs b/utils/TestHelpers/EntityFramework/SqlServerFixture.cs index 18653db..d263558 100644 --- a/utils/TestHelpers/EntityFramework/SqlServerFixture.cs +++ b/utils/TestHelpers/EntityFramework/SqlServerFixture.cs @@ -1,74 +1,73 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Hosting; -using Testcontainers.MsSql; - -namespace TestHelpers.EntityFramework; - -public abstract class SqlServerFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext -{ - public MsSqlContainer SqlServer { get; } = - new MsSqlBuilder() - .WithImage("vibs2006/sql_server_fts") - .WithReuse(true) - .WithLabel("reuse-id", reuseId) - .Build(); - - public IServiceProvider Services { get; private set; } - - public IDbContextFactory DbContextFactory - => Services.GetRequiredService>(); - - public async Task InitializeAsync() - { - await SqlServer.StartAsync(); - await SqlServer.ExecScriptAsync($"create database squidex;"); - - var serviceCollection = - new ServiceCollection() - .AddSingleton>() - .AddDbContextFactory(b => - { - b.UseSqlServer(GetConnectionString()); - }); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await SqlServer.StopAsync(); - } - - private string GetConnectionString() - { - var builder = new SqlConnectionStringBuilder(SqlServer.GetConnectionString()) - { - InitialCatalog = "squidex", - }; - - return builder.ConnectionString; - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Hosting; +using Testcontainers.MsSql; + +namespace TestHelpers.EntityFramework; + +public abstract class SqlServerFixture(string? reuseId = null) : IAsyncLifetime where TContext : DbContext +{ + public MsSqlContainer SqlServer { get; } = + new MsSqlBuilder("vibs2006/sql_server_fts") + .WithReuse(true) + .WithLabel("reuse-id", reuseId) + .Build(); + + public IServiceProvider Services { get; private set; } + + public IDbContextFactory DbContextFactory + => Services.GetRequiredService>(); + + public async Task InitializeAsync() + { + await SqlServer.StartAsync(); + await SqlServer.ExecScriptAsync($"create database squidex;"); + + var serviceCollection = + new ServiceCollection() + .AddSingleton>() + .AddDbContextFactory(b => + { + b.UseSqlServer(GetConnectionString()); + }); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await SqlServer.StopAsync(); + } + + private string GetConnectionString() + { + var builder = new SqlConnectionStringBuilder(SqlServer.GetConnectionString()) + { + InitialCatalog = "squidex", + }; + + return builder.ConnectionString; + } +} diff --git a/utils/TestHelpers/MongoDb/MongoFerretFixture.cs b/utils/TestHelpers/MongoDb/MongoFerretFixture.cs index 12191c2..b386d6f 100644 --- a/utils/TestHelpers/MongoDb/MongoFerretFixture.cs +++ b/utils/TestHelpers/MongoDb/MongoFerretFixture.cs @@ -1,92 +1,91 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics; -using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Containers; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Squidex.Hosting; - -namespace TestHelpers.MongoDb; - -public abstract class MongoFerretFixture(string reuseId = "libs-mongodb") : IAsyncLifetime -{ - public IContainer MongoDb { get; } = - new ContainerBuilder() - .WithReuse(Debugger.IsAttached) - .WithLabel("reuse-id", reuseId) - .WithImage("ghcr.io/ferretdb/ferretdb-eval:2.4") - .WithPortBinding(27017, true) - .WithEnvironment("POSTGRES_USER", "username") - .WithEnvironment("POSTGRES_PASSWORD", "password") - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(27017, o => o.WithTimeout(TimeSpan.FromSeconds(60)))) - .Build(); - - public IServiceProvider Services { get; private set; } - - public IMongoClient MongoClient - => Services.GetRequiredService(); - - public IMongoDatabase MongoDatabase - => Services.GetRequiredService(); - - public async Task InitializeAsync() - { - await MongoDb.StartAsync(); - - var mongoClient = await TryConnectAsync(); - - var serviceCollection = new ServiceCollection() - .AddSingleton(mongoClient) - .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - private async Task TryConnectAsync() - { - using var cts = new CancellationTokenSource(30_000); - while (!cts.Token.IsCancellationRequested) - { - try - { - var mongoClient = new MongoClient($"mongodb://username:password@localhost:{MongoDb.GetMappedPublicPort(27017)}/"); - await mongoClient.ListDatabasesAsync(cts.Token); - - return mongoClient; - } - catch - { - continue; - } - } - - var (stdOut, stdError) = await MongoDb.GetLogsAsync(ct: default); - - throw new InvalidOperationException($"Failed connect to ferred DB\n{stdOut}\n{stdError}"); - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await MongoDb.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; +using Microsoft.Extensions.DependencyInjection; +using MongoDB.Driver; +using Squidex.Hosting; + +namespace TestHelpers.MongoDb; + +public abstract class MongoFerretFixture(string reuseId = "libs-mongodb") : IAsyncLifetime +{ + public IContainer MongoDb { get; } = + new ContainerBuilder("ghcr.io/ferretdb/ferretdb-eval:2.4") + .WithReuse(Debugger.IsAttached) + .WithLabel("reuse-id", reuseId) + .WithPortBinding(27017, true) + .WithEnvironment("POSTGRES_USER", "username") + .WithEnvironment("POSTGRES_PASSWORD", "password") + .WithWaitStrategy(Wait.ForUnixContainer().UntilExternalTcpPortIsAvailable(27017, o => o.WithTimeout(TimeSpan.FromSeconds(60)))) + .Build(); + + public IServiceProvider Services { get; private set; } + + public IMongoClient MongoClient + => Services.GetRequiredService(); + + public IMongoDatabase MongoDatabase + => Services.GetRequiredService(); + + public async Task InitializeAsync() + { + await MongoDb.StartAsync(); + + var mongoClient = await TryConnectAsync(); + + var serviceCollection = new ServiceCollection() + .AddSingleton(mongoClient) + .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + private async Task TryConnectAsync() + { + using var cts = new CancellationTokenSource(30_000); + while (!cts.Token.IsCancellationRequested) + { + try + { + var mongoClient = new MongoClient($"mongodb://username:password@localhost:{MongoDb.GetMappedPublicPort(27017)}/"); + await mongoClient.ListDatabasesAsync(cts.Token); + + return mongoClient; + } + catch + { + continue; + } + } + + var (stdOut, stdError) = await MongoDb.GetLogsAsync(ct: default); + + throw new InvalidOperationException($"Failed connect to ferred DB\n{stdOut}\n{stdError}"); + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await MongoDb.StopAsync(); + } +} diff --git a/utils/TestHelpers/MongoDb/MongoFixture.cs b/utils/TestHelpers/MongoDb/MongoFixture.cs index 3621a16..d36f8dd 100644 --- a/utils/TestHelpers/MongoDb/MongoFixture.cs +++ b/utils/TestHelpers/MongoDb/MongoFixture.cs @@ -1,61 +1,61 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Squidex.Hosting; -using Testcontainers.MongoDb; - -namespace TestHelpers.MongoDb; - -public abstract class MongoFixture(string reuseId = "libs-mongodb") : IAsyncLifetime -{ - public MongoDbContainer MongoDb { get; } = - new MongoDbBuilder() - .WithReuse(Debugger.IsAttached) - .WithLabel("reuse-id", reuseId) - .Build(); - - public IServiceProvider Services { get; private set; } - - public IMongoClient MongoClient - => Services.GetRequiredService(); - - public IMongoDatabase MongoDatabase - => Services.GetRequiredService(); - - public async Task InitializeAsync() - { - await MongoDb.StartAsync(); - - var serviceCollection = new ServiceCollection() - .AddSingleton(_ => new MongoClient(MongoDb.GetConnectionString())) - .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await MongoDb.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using MongoDB.Driver; +using Squidex.Hosting; +using Testcontainers.MongoDb; + +namespace TestHelpers.MongoDb; + +public abstract class MongoFixture(string reuseId = "libs-mongodb") : IAsyncLifetime +{ + public MongoDbContainer MongoDb { get; } = + new MongoDbBuilder("mongo:latest") + .WithReuse(Debugger.IsAttached) + .WithLabel("reuse-id", reuseId) + .Build(); + + public IServiceProvider Services { get; private set; } + + public IMongoClient MongoClient + => Services.GetRequiredService(); + + public IMongoDatabase MongoDatabase + => Services.GetRequiredService(); + + public async Task InitializeAsync() + { + await MongoDb.StartAsync(); + + var serviceCollection = new ServiceCollection() + .AddSingleton(_ => new MongoClient(MongoDb.GetConnectionString())) + .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await MongoDb.StopAsync(); + } +} diff --git a/utils/TestHelpers/MongoDb/MongoReplicaSetFixture.cs b/utils/TestHelpers/MongoDb/MongoReplicaSetFixture.cs index 8ece085..5176f67 100644 --- a/utils/TestHelpers/MongoDb/MongoReplicaSetFixture.cs +++ b/utils/TestHelpers/MongoDb/MongoReplicaSetFixture.cs @@ -1,62 +1,62 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Diagnostics; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Squidex.Hosting; -using Testcontainers.MongoDb; - -namespace TestHelpers.MongoDb; - -public abstract class MongoReplicaSetFixture(string reuseId = "libs-mongodb") : IAsyncLifetime -{ - public MongoDbContainer MongoDb { get; } = - new MongoDbBuilder() - .WithReuse(Debugger.IsAttached) - .WithLabel("reuse-id", reuseId) - .WithReplicaSet() - .Build(); - - public IServiceProvider Services { get; private set; } - - public IMongoClient MongoClient - => Services.GetRequiredService(); - - public IMongoDatabase MongoDatabase - => Services.GetRequiredService(); - - public async Task InitializeAsync() - { - await MongoDb.StartAsync(); - - var serviceCollection = new ServiceCollection() - .AddSingleton(_ => new MongoClient(MongoDb.GetConnectionString())) - .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")); - - AddServices(serviceCollection); - - Services = serviceCollection.BuildServiceProvider(); - - foreach (var service in Services.GetRequiredService>()) - { - await service.InitializeAsync(default); - } - } - - protected abstract void AddServices(IServiceCollection services); - - public async Task DisposeAsync() - { - foreach (var service in Services.GetRequiredService>()) - { - await service.ReleaseAsync(default); - } - - await MongoDb.StopAsync(); - } -} +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using MongoDB.Driver; +using Squidex.Hosting; +using Testcontainers.MongoDb; + +namespace TestHelpers.MongoDb; + +public abstract class MongoReplicaSetFixture(string reuseId = "libs-mongodb") : IAsyncLifetime +{ + public MongoDbContainer MongoDb { get; } = + new MongoDbBuilder("mongo:latest") + .WithReuse(Debugger.IsAttached) + .WithLabel("reuse-id", reuseId) + .WithReplicaSet() + .Build(); + + public IServiceProvider Services { get; private set; } + + public IMongoClient MongoClient + => Services.GetRequiredService(); + + public IMongoDatabase MongoDatabase + => Services.GetRequiredService(); + + public async Task InitializeAsync() + { + await MongoDb.StartAsync(); + + var serviceCollection = new ServiceCollection() + .AddSingleton(_ => new MongoClient(MongoDb.GetConnectionString())) + .AddSingleton(c => c.GetRequiredService().GetDatabase("Test")); + + AddServices(serviceCollection); + + Services = serviceCollection.BuildServiceProvider(); + + foreach (var service in Services.GetRequiredService>()) + { + await service.InitializeAsync(default); + } + } + + protected abstract void AddServices(IServiceCollection services); + + public async Task DisposeAsync() + { + foreach (var service in Services.GetRequiredService>()) + { + await service.ReleaseAsync(default); + } + + await MongoDb.StopAsync(); + } +} diff --git a/utils/TestHelpers/TestHelpers.csproj b/utils/TestHelpers/TestHelpers.csproj index f60edff..8b69e04 100644 --- a/utils/TestHelpers/TestHelpers.csproj +++ b/utils/TestHelpers/TestHelpers.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Latest enable enable @@ -10,36 +10,35 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive