Browse Source

Standalone (#1042)

* Started with migration.

* Fix.

* Fix import order.

* Fix storybook.

* Fix copy

* Fix hash and regex.

* Improve hash provider again.
pull/1043/head
Sebastian Stehle 3 years ago
committed by GitHub
parent
commit
4ecb4e4942
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Dockerfile
  2. 2
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  3. 12
      backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs
  4. 2
      backend/i18n/translator/Squidex.Translator/Squidex.Translator.csproj
  5. 2
      backend/src/Migrations/Migrations.csproj
  6. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  7. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs
  8. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  9. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  10. 11
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesClient.cs
  11. 2
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  12. 2
      backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  13. 2
      backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  14. 2
      backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  15. 2
      backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
  16. 2
      backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
  17. 2
      backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  18. 7
      backend/src/Squidex.Infrastructure/Language.cs
  19. 2
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  20. 12
      backend/src/Squidex.Infrastructure/StringExtensions.cs
  21. 9
      backend/src/Squidex.Shared/DefaultClients.cs
  22. 35
      backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs
  23. 325
      backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs
  24. 35
      backend/src/Squidex.Shared/PermissionExtensions.cs
  25. 419
      backend/src/Squidex.Shared/PermissionIds.cs
  26. 3
      backend/src/Squidex.Shared/Squidex.Shared.csproj
  27. 15
      backend/src/Squidex.Shared/Texts.cs
  28. 63
      backend/src/Squidex.Shared/Users/ClientUser.cs
  29. 17
      backend/src/Squidex.Shared/Users/IUser.cs
  30. 35
      backend/src/Squidex.Shared/Users/IUserResolver.cs
  31. 7
      backend/src/Squidex.Web/IgnoreHashFileProvider.cs
  32. 2
      backend/src/Squidex.Web/Squidex.Web.csproj
  33. 2
      backend/src/Squidex/Squidex.csproj
  34. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  35. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj
  36. 2
      backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
  37. 2
      backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj
  38. 2
      backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj
  39. 2
      backend/tools/GenerateLanguages/GenerateLanguages.csproj
  40. 13
      frontend/.storybook/main.js
  41. 15
      frontend/angular.json
  42. 383
      frontend/package-lock.json
  43. 2
      frontend/package.json
  44. 20
      frontend/src/app/app.component.ts
  45. 129
      frontend/src/app/app.module.ts
  46. 43
      frontend/src/app/app.routes.ts
  47. 22
      frontend/src/app/assets/squid.svg
  48. 15
      frontend/src/app/features/administration/administration-area.component.ts
  49. 16
      frontend/src/app/features/administration/declarations.ts
  50. 44
      frontend/src/app/features/administration/guards/user-must-exist.guard.spec.ts
  51. 47
      frontend/src/app/features/administration/guards/user-must-exist.guard.ts
  52. 9
      frontend/src/app/features/administration/pages/event-consumers/event-consumer.component.ts
  53. 27
      frontend/src/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  54. 25
      frontend/src/app/features/administration/pages/restore/restore-page.component.ts
  55. 20
      frontend/src/app/features/administration/pages/users/user-page.component.ts
  56. 15
      frontend/src/app/features/administration/pages/users/user.component.ts
  57. 31
      frontend/src/app/features/administration/pages/users/users-page.component.ts
  58. 52
      frontend/src/app/features/administration/routes.ts
  59. 2
      frontend/src/app/features/administration/services/event-consumers.service.spec.ts
  60. 2
      frontend/src/app/features/administration/services/users.service.spec.ts
  61. 6
      frontend/src/app/features/administration/state/event-consumers.state.spec.ts
  62. 2
      frontend/src/app/features/administration/state/event-consumers.state.ts
  63. 2
      frontend/src/app/features/administration/state/users.forms.ts
  64. 4
      frontend/src/app/features/administration/state/users.state.spec.ts
  65. 2
      frontend/src/app/features/administration/state/users.state.ts
  66. 14
      frontend/src/app/features/api/api-area.component.ts
  67. 9
      frontend/src/app/features/api/declarations.ts
  68. 9
      frontend/src/app/features/api/index.ts
  69. 37
      frontend/src/app/features/api/module.ts
  70. 19
      frontend/src/app/features/api/pages/graphql/graphql-page.component.ts
  71. 23
      frontend/src/app/features/api/routes.ts
  72. 12
      frontend/src/app/features/apps/declarations.ts
  73. 9
      frontend/src/app/features/apps/index.ts
  74. 34
      frontend/src/app/features/apps/module.ts
  75. 17
      frontend/src/app/features/apps/pages/app.component.ts
  76. 24
      frontend/src/app/features/apps/pages/apps-page.component.ts
  77. 11
      frontend/src/app/features/apps/pages/news-dialog.component.ts
  78. 16
      frontend/src/app/features/apps/pages/onboarding-dialog.component.ts
  79. 13
      frontend/src/app/features/apps/pages/team.component.ts
  80. 16
      frontend/src/app/features/apps/routes.ts
  81. 11
      frontend/src/app/features/assets/declarations.ts
  82. 9
      frontend/src/app/features/assets/index.ts
  83. 39
      frontend/src/app/features/assets/module.ts
  84. 15
      frontend/src/app/features/assets/pages/asset-tag-dialog.component.ts
  85. 13
      frontend/src/app/features/assets/pages/asset-tags.component.ts
  86. 12
      frontend/src/app/features/assets/pages/assets-filters-page.component.ts
  87. 31
      frontend/src/app/features/assets/pages/assets-page.component.ts
  88. 23
      frontend/src/app/features/assets/routes.ts
  89. 43
      frontend/src/app/features/content/declarations.ts
  90. 9
      frontend/src/app/features/content/index.ts
  91. 135
      frontend/src/app/features/content/module.ts
  92. 23
      frontend/src/app/features/content/pages/calendar/calendar-page.component.ts
  93. 8
      frontend/src/app/features/content/pages/comments/comments-page.component.ts
  94. 13
      frontend/src/app/features/content/pages/content/content-event.component.ts
  95. 25
      frontend/src/app/features/content/pages/content/content-history-page.component.ts
  96. 44
      frontend/src/app/features/content/pages/content/content-page.component.ts
  97. 20
      frontend/src/app/features/content/pages/content/editor/content-editor.component.ts
  98. 13
      frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts
  99. 18
      frontend/src/app/features/content/pages/content/references/content-references.component.ts
  100. 12
      frontend/src/app/features/content/pages/contents/contents-filters-page.component.ts

2
Dockerfile

@ -86,7 +86,7 @@ WORKDIR /app
# Copy from build stages # Copy from build stages
COPY --from=backend /build/ . COPY --from=backend /build/ .
COPY --from=frontend /build/ wwwroot/build/ COPY --from=frontend /build/browser wwwroot/build/
EXPOSE 80 EXPOSE 80
EXPOSE 443 EXPOSE 443

2
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

12
backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs

@ -15,10 +15,10 @@ using Squidex.Infrastructure.Json;
namespace Squidex.Extensions.Text.ElasticSearch; namespace Squidex.Extensions.Text.ElasticSearch;
public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable public sealed partial class ElasticSearchTextIndex : ITextIndex, IInitializable
{ {
private static readonly Regex LanguageRegex = new Regex(@"[^\w]+([a-z\-_]{2,}):", RegexOptions.Compiled | RegexOptions.ExplicitCapture); private static readonly Regex RegexLanguageNormal = BuildLanguageRegexNormal();
private static readonly Regex LanguageRegexStart = new Regex(@"$^([a-z\-_]{2,}):", RegexOptions.Compiled | RegexOptions.ExplicitCapture); private static readonly Regex RegexLanguageStart = BuildLanguageRegexStart();
private readonly IJsonSerializer jsonSerializer; private readonly IJsonSerializer jsonSerializer;
private readonly IElasticSearchClient elasticClient; private readonly IElasticSearchClient elasticClient;
private readonly QueryParser queryParser = new QueryParser(ElasticSearchIndexDefinition.GetFieldPath); private readonly QueryParser queryParser = new QueryParser(ElasticSearchIndexDefinition.GetFieldPath);
@ -227,4 +227,10 @@ public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable
{ {
return scope == SearchScope.Published ? "servePublished" : "serveAll"; return scope == SearchScope.Published ? "servePublished" : "serveAll";
} }
[GeneratedRegex("[^\\w]+([a-z\\-_]{2,}):", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildLanguageRegexNormal();
[GeneratedRegex("$^([a-z\\-_]{2,}):", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildLanguageRegexStart();
} }

2
backend/i18n/translator/Squidex.Translator/Squidex.Translator.csproj

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NoWarn>NU1608</NoWarn> <NoWarn>NU1608</NoWarn>
</PropertyGroup> </PropertyGroup>

2
backend/src/Migrations/Migrations.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

11
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs

@ -23,7 +23,7 @@ using Squidex.Text;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
public sealed class ScriptingCompleter public sealed partial class ScriptingCompleter
{ {
private readonly IEnumerable<IScriptDescriptor> descriptors; private readonly IEnumerable<IScriptDescriptor> descriptors;
private static readonly FilterSchema DynamicData = new FilterSchema(FilterSchemaType.Object) private static readonly FilterSchema DynamicData = new FilterSchema(FilterSchemaType.Object)
@ -112,9 +112,9 @@ public sealed class ScriptingCompleter
return new Process(descriptors).UsageTrigger(); return new Process(descriptors).UsageTrigger();
} }
private sealed class Process private sealed partial class Process
{ {
private static readonly Regex PropertyRegex = new Regex(@"^(?!\d)[\w$]+$", RegexOptions.Compiled); private static readonly Regex RegexProperty = BuildPropertyRegex();
private readonly Stack<string> prefixes = new Stack<string>(); private readonly Stack<string> prefixes = new Stack<string>();
private readonly Dictionary<string, ScriptingValue> result = new Dictionary<string, ScriptingValue>(); private readonly Dictionary<string, ScriptingValue> result = new Dictionary<string, ScriptingValue>();
private readonly IEnumerable<IScriptDescriptor> descriptors; private readonly IEnumerable<IScriptDescriptor> descriptors;
@ -523,7 +523,7 @@ public sealed class ScriptingCompleter
{ {
prefixes.Push(name); prefixes.Push(name);
} }
else if (PropertyRegex.IsMatch(name)) else if (RegexProperty.IsMatch(name))
{ {
prefixes.Push($".{name}"); prefixes.Push($".{name}");
} }
@ -532,5 +532,8 @@ public sealed class ScriptingCompleter
prefixes.Push($"['{name}']"); prefixes.Push($"['{name}']");
} }
} }
[GeneratedRegex("^(?!\\d)[\\w$]+$", RegexOptions.Compiled)]
private static partial Regex BuildPropertyRegex();
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

11
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplatesClient.cs

@ -11,9 +11,9 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Templates; namespace Squidex.Domain.Apps.Entities.Apps.Templates;
public sealed class TemplatesClient public sealed partial class TemplatesClient
{ {
private static readonly Regex Regex = new Regex("\\* \\[(?<Title>.*)\\]\\((?<Name>.*)\\/README\\.md\\): (?<Description>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture); private static readonly Regex RegexTemplate = BuildTemplateRegex();
private readonly IHttpClientFactory httpClientFactory; private readonly IHttpClientFactory httpClientFactory;
private readonly TemplatesOptions options; private readonly TemplatesOptions options;
@ -34,7 +34,7 @@ public sealed class TemplatesClient
var text = await httpClient.GetStringAsync(url, ct); var text = await httpClient.GetStringAsync(url, ct);
foreach (Match match in Regex.Matches(text).OfType<Match>()) foreach (var match in RegexTemplate.Matches(text).OfType<Match>())
{ {
var currentName = match.Groups["Name"].Value; var currentName = match.Groups["Name"].Value;
@ -61,7 +61,7 @@ public sealed class TemplatesClient
var text = await httpClient.GetStringAsync(url, ct); var text = await httpClient.GetStringAsync(url, ct);
foreach (Match match in Regex.Matches(text).OfType<Match>()) foreach (Match match in RegexTemplate.Matches(text).OfType<Match>())
{ {
var title = match.Groups["Title"].Value; var title = match.Groups["Title"].Value;
@ -97,4 +97,7 @@ public sealed class TemplatesClient
return null; return null;
} }
[GeneratedRegex("\\* \\[(?<Title>.*)\\]\\((?<Name>.*)\\/README\\.md\\): (?<Description>.*)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildTemplateRegex();
} }

2
backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

2
backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace> <RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace> <RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace> <RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

7
backend/src/Squidex.Infrastructure/Language.cs

@ -14,7 +14,7 @@ namespace Squidex.Infrastructure;
[TypeConverter(typeof(LanguageTypeConverter))] [TypeConverter(typeof(LanguageTypeConverter))]
public partial record Language public partial record Language
{ {
private static readonly Regex CultureRegex = new Regex("^(?<Code>[a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); private static readonly Regex RegexCulture = BuildCultureRegex();
public static Language GetLanguage(string iso2Code) public static Language GetLanguage(string iso2Code)
{ {
@ -85,7 +85,7 @@ public partial record Language
if (input.Length != 2) if (input.Length != 2)
{ {
var match = CultureRegex.Match(input); var match = RegexCulture.Match(input);
if (!match.Success) if (!match.Success)
{ {
@ -107,4 +107,7 @@ public partial record Language
{ {
return EnglishName; return EnglishName;
} }
[GeneratedRegex("^(?<Code>[a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, "en-US")]
private static partial Regex BuildCultureRegex();
} }

2
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

12
backend/src/Squidex.Infrastructure/StringExtensions.cs

@ -13,10 +13,10 @@ using System.Text.RegularExpressions;
namespace Squidex.Infrastructure; namespace Squidex.Infrastructure;
public static class StringExtensions public static partial class StringExtensions
{ {
private static readonly Regex RegexEmail = new Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-||_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+([a-z]+|\d|-|\.{0,1}|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); private static readonly Regex RegexEmail = BuildEmailRegex();
private static readonly Regex RegexProperty = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); private static readonly Regex RegexProperty = BuildPropertyRegex();
private static readonly JsonSerializerOptions JsonEscapeOptions = new JsonSerializerOptions private static readonly JsonSerializerOptions JsonEscapeOptions = new JsonSerializerOptions
{ {
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
@ -113,4 +113,10 @@ public static class StringExtensions
return sb; return sb;
} }
[GeneratedRegex("^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-||_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+([a-z]+|\\d|-|\\.{0,1}|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])?([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled, "en-US")]
private static partial Regex BuildEmailRegex();
[GeneratedRegex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildPropertyRegex();
} }

9
backend/src/Squidex.Shared/DefaultClients.cs

@ -5,10 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Shared namespace Squidex.Shared;
public static class DefaultClients
{ {
public static class DefaultClients public const string Frontend = "squidex-frontend";
{
public const string Frontend = "squidex-frontend";
}
} }

35
backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs

@ -5,36 +5,35 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Shared.Identity namespace Squidex.Shared.Identity;
public static class SquidexClaimTypes
{ {
public static class SquidexClaimTypes public const string ClientSecret = "urn:squidex:clientSecret";
{
public const string ClientSecret = "urn:squidex:clientSecret";
public const string Consent = "urn:squidex:consent"; public const string Consent = "urn:squidex:consent";
public const string ConsentForEmails = "urn:squidex:consent:emails"; public const string ConsentForEmails = "urn:squidex:consent:emails";
public const string Custom = "urn:squidex:custom"; public const string Custom = "urn:squidex:custom";
public const string DisplayName = "urn:squidex:name"; public const string DisplayName = "urn:squidex:name";
public const string Hidden = "urn:squidex:hidden"; public const string Hidden = "urn:squidex:hidden";
public const string Invited = "urn:squidex:invited"; public const string Invited = "urn:squidex:invited";
public const string NotifoKey = "urn:squidex:notifo"; public const string NotifoKey = "urn:squidex:notifo";
public const string Permissions = "urn:squidex:permissions"; public const string Permissions = "urn:squidex:permissions";
public const string PictureUrl = "urn:squidex:picture"; public const string PictureUrl = "urn:squidex:picture";
public const string PictureUrlStore = "store"; public const string PictureUrlStore = "store";
public const string Answer = "urn:squidex:answer"; public const string Answer = "urn:squidex:answer";
public const string TotalApps = "urn:squidex:internal:totalApps"; public const string TotalApps = "urn:squidex:internal:totalApps";
public const string UIProperty = "urn:squidex:ui"; public const string UIProperty = "urn:squidex:ui";
}
} }

325
backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs

@ -15,236 +15,241 @@ using System.Text.RegularExpressions;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
namespace Squidex.Shared.Identity namespace Squidex.Shared.Identity;
public static partial class SquidexClaimsExtensions
{ {
public static class SquidexClaimsExtensions private const string ClientPrefix = "client_";
{
private const string ClientPrefix = "client_";
private static readonly Regex KeyValueAppClaim = new Regex("(?<App>[\\S]+),(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.Compiled); private static readonly Regex RegexKeyValueAppClaim = BuildKeyValueAppClaimRegex();
private static readonly Regex KeyValueClaim = new Regex("(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.Compiled); private static readonly Regex RegexKeyValueClaim = BuildKeyValueClaimRegex();
public static PermissionSet Permissions(this IEnumerable<Claim> user) public static PermissionSet Permissions(this IEnumerable<Claim> user)
{ {
var permissions = user.GetClaims(SquidexClaimTypes.Permissions).Select(x => x.Value); var permissions = user.GetClaims(SquidexClaimTypes.Permissions).Select(x => x.Value);
return new PermissionSet(permissions); return new PermissionSet(permissions);
} }
public static bool IsHidden(this IEnumerable<Claim> user) public static bool IsHidden(this IEnumerable<Claim> user)
{ {
return user.HasClaimValue(SquidexClaimTypes.Hidden, "true"); return user.HasClaimValue(SquidexClaimTypes.Hidden, "true");
} }
public static bool HasConsent(this IEnumerable<Claim> user) public static bool HasConsent(this IEnumerable<Claim> user)
{ {
return user.HasClaimValue(SquidexClaimTypes.Consent, "true"); return user.HasClaimValue(SquidexClaimTypes.Consent, "true");
} }
public static bool HasConsentForEmails(this IEnumerable<Claim> user) public static bool HasConsentForEmails(this IEnumerable<Claim> user)
{ {
return user.HasClaimValue(SquidexClaimTypes.ConsentForEmails, "true"); return user.HasClaimValue(SquidexClaimTypes.ConsentForEmails, "true");
} }
public static bool HasDisplayName(this IEnumerable<Claim> user) public static bool HasDisplayName(this IEnumerable<Claim> user)
{ {
return user.HasClaim(SquidexClaimTypes.DisplayName); return user.HasClaim(SquidexClaimTypes.DisplayName);
} }
public static bool HasPictureUrl(this IEnumerable<Claim> user) public static bool HasPictureUrl(this IEnumerable<Claim> user)
{ {
return user.HasClaim(SquidexClaimTypes.PictureUrl); return user.HasClaim(SquidexClaimTypes.PictureUrl);
} }
public static bool IsPictureUrlStored(this IEnumerable<Claim> user) public static bool IsPictureUrlStored(this IEnumerable<Claim> user)
{ {
return user.HasClaimValue(SquidexClaimTypes.PictureUrl, SquidexClaimTypes.PictureUrlStore); return user.HasClaimValue(SquidexClaimTypes.PictureUrl, SquidexClaimTypes.PictureUrlStore);
} }
public static string? ClientSecret(this IEnumerable<Claim> user) public static string? ClientSecret(this IEnumerable<Claim> user)
{ {
return user.GetClaimValue(SquidexClaimTypes.ClientSecret); return user.GetClaimValue(SquidexClaimTypes.ClientSecret);
} }
public static string? PictureUrl(this IEnumerable<Claim> user) public static string? PictureUrl(this IEnumerable<Claim> user)
{ {
return user.GetClaimValue(SquidexClaimTypes.PictureUrl); return user.GetClaimValue(SquidexClaimTypes.PictureUrl);
} }
public static string? DisplayName(this IEnumerable<Claim> user) public static string? DisplayName(this IEnumerable<Claim> user)
{ {
return user.GetClaimValue(SquidexClaimTypes.DisplayName); return user.GetClaimValue(SquidexClaimTypes.DisplayName);
} }
public static string? Answer(this IEnumerable<Claim> user, string name) public static string? Answer(this IEnumerable<Claim> user, string name)
{ {
var prefix = $"{name}="; var prefix = $"{name}=";
foreach (var claim in user) foreach (var claim in user)
{
if (claim.Type == SquidexClaimTypes.Answer && claim.Value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{ {
if (claim.Type == SquidexClaimTypes.Answer && claim.Value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return claim.Value[prefix.Length..];
{
return claim.Value[prefix.Length..];
}
} }
return null;
} }
public static int GetTotalApps(this IEnumerable<Claim> user) return null;
{ }
var value = user.GetClaimValue(SquidexClaimTypes.TotalApps);
int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result); public static int GetTotalApps(this IEnumerable<Claim> user)
{
var value = user.GetClaimValue(SquidexClaimTypes.TotalApps);
return result; int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result);
}
public static bool HasClaim(this IEnumerable<Claim> user, string type) return result;
{ }
return user.GetClaims(type).Any();
}
public static bool HasClaimValue(this IEnumerable<Claim> user, string type, string value) public static bool HasClaim(this IEnumerable<Claim> user, string type)
{ {
return user.GetClaims(type).Any(x => string.Equals(x.Value, value, StringComparison.OrdinalIgnoreCase)); return user.GetClaims(type).Any();
} }
public static IEnumerable<Claim> GetSquidexClaims(this IEnumerable<Claim> user) public static bool HasClaimValue(this IEnumerable<Claim> user, string type, string value)
{
return user.GetClaims(type).Any(x => string.Equals(x.Value, value, StringComparison.OrdinalIgnoreCase));
}
public static IEnumerable<Claim> GetSquidexClaims(this IEnumerable<Claim> user)
{
const string prefix = "urn:squidex:";
foreach (var claim in user)
{ {
const string prefix = "urn:squidex:"; var type = GetType(claim);
foreach (var claim in user) if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{ {
var type = GetType(claim); yield return claim;
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
yield return claim;
}
} }
} }
}
public static IEnumerable<(string Name, string Value)> GetCustomProperties(this IEnumerable<Claim> user) public static IEnumerable<(string Name, string Value)> GetCustomProperties(this IEnumerable<Claim> user)
{
var prefix = $"{SquidexClaimTypes.Custom}:";
foreach (var claim in user)
{ {
var prefix = $"{SquidexClaimTypes.Custom}:"; var type = GetType(claim);
foreach (var claim in user) if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{ {
var type = GetType(claim); var name = type[prefix.Length..].ToString();
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) yield return (name.Trim(), claim.Value.Trim());
{ }
var name = type[prefix.Length..].ToString(); else if (type.Equals(SquidexClaimTypes.Custom, StringComparison.OrdinalIgnoreCase))
{
var match = RegexKeyValueClaim.Match(claim.Value);
yield return (name.Trim(), claim.Value.Trim()); if (match.Success)
}
else if (type.Equals(SquidexClaimTypes.Custom, StringComparison.OrdinalIgnoreCase))
{ {
var match = KeyValueClaim.Match(claim.Value); yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim());
if (match.Success)
{
yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim());
}
} }
} }
} }
}
public static IEnumerable<(string Name, JsonValue Value)> GetUIProperties(this IEnumerable<Claim> user, string app) public static IEnumerable<(string Name, JsonValue Value)> GetUIProperties(this IEnumerable<Claim> user, string app)
{
var prefix = $"{SquidexClaimTypes.UIProperty}:{app}:";
static JsonValue Parse(string value)
{ {
var prefix = $"{SquidexClaimTypes.UIProperty}:{app}:"; value = value.Trim();
static JsonValue Parse(string value) try
{ {
value = value.Trim(); var root = JsonDocument.Parse(value).RootElement;
try
{
var root = JsonDocument.Parse(value).RootElement;
return JsonValue.Create(root); return JsonValue.Create(root);
}
catch
{
return JsonValue.Create(value);
}
} }
catch
foreach (var claim in user)
{ {
var type = GetType(claim); return JsonValue.Create(value);
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var name = type[prefix.Length..].ToString();
yield return (name.Trim(), Parse(claim.Value));
}
else if (type.Equals(SquidexClaimTypes.UIProperty, StringComparison.OrdinalIgnoreCase))
{
if (!claim.Value.StartsWith(app, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var match = KeyValueAppClaim.Match(claim.Value);
if (match.Success)
{
yield return (match.Groups["Key"].Value.Trim(), Parse(match.Groups["Value"].Value));
}
}
} }
} }
public static string? PictureNormalizedUrl(this IEnumerable<Claim> user) foreach (var claim in user)
{ {
var url = user.FirstOrDefault(x => x.Type == SquidexClaimTypes.PictureUrl)?.Value; var type = GetType(claim);
if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var name = type[prefix.Length..].ToString();
if (Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar", StringComparison.Ordinal)) yield return (name.Trim(), Parse(claim.Value));
}
else if (type.Equals(SquidexClaimTypes.UIProperty, StringComparison.OrdinalIgnoreCase))
{ {
if (url.Contains('?', StringComparison.Ordinal)) if (!claim.Value.StartsWith(app, StringComparison.OrdinalIgnoreCase))
{ {
url += "&d=404"; continue;
} }
else
var match = RegexKeyValueAppClaim.Match(claim.Value);
if (match.Success)
{ {
url += "?d=404"; yield return (match.Groups["Key"].Value.Trim(), Parse(match.Groups["Value"].Value));
} }
} }
return url;
} }
}
private static string? GetClaimValue(this IEnumerable<Claim> user, string type) public static string? PictureNormalizedUrl(this IEnumerable<Claim> user)
{ {
return user.GetClaims(type).FirstOrDefault()?.Value; var url = user.FirstOrDefault(x => x.Type == SquidexClaimTypes.PictureUrl)?.Value;
}
private static IEnumerable<Claim> GetClaims(this IEnumerable<Claim> user, string request) if (Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar", StringComparison.Ordinal))
{ {
foreach (var claim in user) if (url.Contains('?', StringComparison.Ordinal))
{ {
var type = GetType(claim); url += "&d=404";
}
if (type.Equals(request, StringComparison.OrdinalIgnoreCase)) else
{ {
yield return claim; url += "?d=404";
}
} }
} }
private static ReadOnlySpan<char> GetType(Claim claim) return url;
}
private static string? GetClaimValue(this IEnumerable<Claim> user, string type)
{
return user.GetClaims(type).FirstOrDefault()?.Value;
}
private static IEnumerable<Claim> GetClaims(this IEnumerable<Claim> user, string request)
{
foreach (var claim in user)
{ {
var type = claim.Type.AsSpan(); var type = GetType(claim);
if (type.StartsWith(ClientPrefix, StringComparison.OrdinalIgnoreCase)) if (type.Equals(request, StringComparison.OrdinalIgnoreCase))
{ {
type = type[ClientPrefix.Length..]; yield return claim;
} }
}
}
private static ReadOnlySpan<char> GetType(Claim claim)
{
var type = claim.Type.AsSpan();
return type; if (type.StartsWith(ClientPrefix, StringComparison.OrdinalIgnoreCase))
{
type = type[ClientPrefix.Length..];
} }
return type;
} }
[GeneratedRegex("(?<App>[\\S]+),(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildKeyValueAppClaimRegex();
[GeneratedRegex("(?<Key>[^=]+)=(?<Value>.+)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
private static partial Regex BuildKeyValueClaimRegex();
} }

35
backend/src/Squidex.Shared/PermissionExtensions.cs

@ -8,29 +8,28 @@
using System.Linq; using System.Linq;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
namespace Squidex.Shared namespace Squidex.Shared;
public static class PermissionExtensions
{ {
public static class PermissionExtensions public static bool Allows(this PermissionSet permissions, string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{ {
public static bool Allows(this PermissionSet permissions, string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any) var permission = PermissionIds.ForApp(id, app, schema, team);
{
var permission = PermissionIds.ForApp(id, app, schema, team);
return permissions.Allows(permission); return permissions.Allows(permission);
} }
public static string[] ToAppNames(this PermissionSet permissions) public static string[] ToAppNames(this PermissionSet permissions)
{ {
var matching = permissions.Where(x => x.StartsWith("squidex.apps.")); var matching = permissions.Where(x => x.StartsWith("squidex.apps."));
var result = var result =
matching matching
.Select(x => x.Id.Split('.')).Where(x => x.Length > 2) .Select(x => x.Id.Split('.')).Where(x => x.Length > 2)
.Select(x => x[2]) .Select(x => x[2])
.Distinct() .Distinct()
.ToArray(); .ToArray();
return result; return result;
}
} }
} }

419
backend/src/Squidex.Shared/PermissionIds.cs

@ -9,219 +9,218 @@ using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
namespace Squidex.Shared namespace Squidex.Shared;
public static class PermissionIds
{ {
public static class PermissionIds public const string All = "squidex.*";
public const string Admin = "squidex.admin.*";
// Admin App Creation
public const string AdminAppCreate = "squidex.admin.apps.create";
// Admin Team Creation
public const string AdminTeamCreate = "squidex.admin.teams.create";
// Backup Admin
public const string AdminRestore = "squidex.admin.restore";
// Event Admin
public const string AdminEvents = "squidex.admin.events";
public const string AdminEventsRead = "squidex.admin.events.read";
public const string AdminEventsManage = "squidex.admin.events.manage";
// User Admin
public const string AdminUsers = "squidex.admin.users";
public const string AdminUsersRead = "squidex.admin.users.read";
public const string AdminUsersCreate = "squidex.admin.users.create";
public const string AdminUsersUpdate = "squidex.admin.users.update";
public const string AdminUsersUnlock = "squidex.admin.users.unlock";
public const string AdminUsersLock = "squidex.admin.users.lock";
// Team
public const string Team = "squidex.teams.{team}";
// Team Transfer
public const string Transfer = "squidex.transfer";
// Team General
public const string TeamAdmin = "squidex.teams.{team}.*";
public const string TeamUpdate = "squidex.teams.{team}.update";
// Team Contributors
public const string TeamContributors = "squidex.teams.{team}.contributors";
public const string TeamContributorsRead = "squidex.teams.{team}.contributors.read";
public const string TeamContributorsAssign = "squidex.teams.{team}.contributors.assign";
public const string TeamContributorsRevoke = "squidex.teams.{team}.contributors.revoke";
// Team Plans
public const string TeamPlans = "squidex.teams.{team}.plans";
public const string TeamPlansRead = "squidex.teams.{team}.plans.read";
public const string TeamPlansChange = "squidex.teams.{team}.plans.change";
// Team Usage
public const string TeamUsage = "squidex.teams.{team}.usage";
// Team History
public const string TeamHistory = "squidex.teams.{team}.history";
// App
public const string App = "squidex.apps.{app}";
// App
public const string AppNoScripting = "squidex.apps.{app}.no-scripting";
// App General
public const string AppAdmin = "squidex.apps.{app}.*";
public const string AppDelete = "squidex.apps.{app}.delete";
public const string AppTransfer = "squidex.apps.{app}.transfer";
public const string AppUpdate = "squidex.apps.{app}.update";
public const string AppUpdateSettings = "squidex.apps.{app}.settings";
// App Image
public const string AppImageUpload = "squidex.apps.{app}.image";
public const string AppImageDelete = "squidex.apps.{app}.image";
// App History
public const string AppHistory = "squidex.apps.{app}.history";
// App Ping
public const string AppPing = "squidex.apps.{app}.ping";
// App Search
public const string AppSearch = "squidex.apps.{app}.search";
// App Translate
public const string AppTranslate = "squidex.apps.{app}.translate";
// App Usage
public const string AppUsage = "squidex.apps.{app}.usage";
// App Comments
public const string AppComments = "squidex.apps.{app}.comments";
public const string AppCommentsRead = "squidex.apps.{app}.comments.read";
public const string AppCommentsCreate = "squidex.apps.{app}.comments.create";
public const string AppCommentsUpdate = "squidex.apps.{app}.comments.update";
public const string AppCommentsDelete = "squidex.apps.{app}.comments.delete";
// App Clients
public const string AppClients = "squidex.apps.{app}.clients";
public const string AppClientsRead = "squidex.apps.{app}.clients.read";
public const string AppClientsCreate = "squidex.apps.{app}.clients.create";
public const string AppClientsUpdate = "squidex.apps.{app}.clients.update";
public const string AppClientsDelete = "squidex.apps.{app}.clients.delete";
// App Contributors
public const string AppContributors = "squidex.apps.{app}.contributors";
public const string AppContributorsRead = "squidex.apps.{app}.contributors.read";
public const string AppContributorsAssign = "squidex.apps.{app}.contributors.assign";
public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke";
// App Languages
public const string AppLanguages = "squidex.apps.{app}.languages";
public const string AppLanguagesRead = "squidex.apps.{app}.languages.read";
public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create";
public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update";
public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete";
// App Roles
public const string AppRoles = "squidex.apps.{app}.roles";
public const string AppRolesRead = "squidex.apps.{app}.roles.read";
public const string AppRolesCreate = "squidex.apps.{app}.roles.create";
public const string AppRolesUpdate = "squidex.apps.{app}.roles.update";
public const string AppRolesDelete = "squidex.apps.{app}.roles.delete";
// App Workflows
public const string AppWorkflows = "squidex.apps.{app}.workflows";
public const string AppWorkflowsRead = "squidex.apps.{app}.workflows.read";
public const string AppWorkflowsCreate = "squidex.apps.{app}.workflows.create";
public const string AppWorkflowsUpdate = "squidex.apps.{app}.workflows.update";
public const string AppWorkflowsDelete = "squidex.apps.{app}.workflows.delete";
// App Backups
public const string AppBackups = "squidex.apps.{app}.backups";
public const string AppBackupsRead = "squidex.apps.{app}.backups.read";
public const string AppBackupsCreate = "squidex.apps.{app}.backups.create";
public const string AppBackupsDelete = "squidex.apps.{app}.backups.delete";
public const string AppBackupsDownload = "squidex.apps.{app}.backups.download";
// App Plans
public const string AppPlans = "squidex.apps.{app}.plans";
public const string AppPlansRead = "squidex.apps.{app}.plans.read";
public const string AppPlansChange = "squidex.apps.{app}.plans.change";
// App Assets
public const string AppAssets = "squidex.apps.{app}.assets";
public const string AppAssetsRead = "squidex.apps.{app}.assets.read";
public const string AppAssetsCreate = "squidex.apps.{app}.assets.create";
public const string AppAssetsUpload = "squidex.apps.{app}.assets.upload";
public const string AppAssetsUpdate = "squidex.apps.{app}.assets.update";
public const string AppAssetsDelete = "squidex.apps.{app}.assets.delete";
// App Asset Folders
public const string AppAssetFolders = "squidex.apps.{app}.assets.folders";
public const string AppAssetFoldersCreate = "squidex.apps.{app}.assets.folders.create";
public const string AppAssetFoldersUpdate = "squidex.apps.{app}.assets.folders.update";
public const string AppAssetFoldersDelete = "squidex.apps.{app}.assets.folders.delete";
// App Asset Scripts
public const string AppAssetScripts = "squidex.apps.{app}.asset-scripts";
public const string AppAssetSScriptsRead = "squidex.apps.{app}.asset-scripts.read";
public const string AppAssetsScriptsUpdate = "squidex.apps.{app}.asset-scripts.update";
// App Rules
public const string AppRules = "squidex.apps.{app}.rules";
public const string AppRulesRead = "squidex.apps.{app}.rules.read";
public const string AppRulesCreate = "squidex.apps.{app}.rules.create";
public const string AppRulesUpdate = "squidex.apps.{app}.rules.update";
public const string AppRulesDisable = "squidex.apps.{app}.rules.disable";
public const string AppRulesDelete = "squidex.apps.{app}.rules.delete";
// App Rule Events
public const string AppRulesEvents = "squidex.apps.{app}.rules.events";
public const string AppRulesEventsRun = "squidex.apps.{app}.rules.events.run";
public const string AppRulesEventsRead = "squidex.apps.{app}.rules.events.read";
public const string AppRulesEventsUpdate = "squidex.apps.{app}.rules.events.update";
public const string AppRulesEventsDelete = "squidex.apps.{app}.rules.events.delete";
// App Schemas
public const string AppSchemas = "squidex.apps.{app}.schemas";
public const string AppSchemasRead = "squidex.apps.{app}.schemas.read";
public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create";
public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{schema}.update";
public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{schema}.scripts";
public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{schema}.publish";
public const string AppSchemasDelete = "squidex.apps.{app}.schemas.{schema}.delete";
// App Contents
public const string AppContents = "squidex.apps.{app}.contents.{schema}";
public const string AppContentsRead = "squidex.apps.{app}.contents.{schema}.read";
public const string AppContentsReadOwn = "squidex.apps.{app}.contents.{schema}.read.own";
public const string AppContentsCreate = "squidex.apps.{app}.contents.{schema}.create";
public const string AppContentsUpdate = "squidex.apps.{app}.contents.{schema}.update";
public const string AppContentsUpdateOwn = "squidex.apps.{app}.contents.{schema}.update.own";
public const string AppContentsChangeStatusCancel = "squidex.apps.{app}.contents.{schema}.changestatus.cancel";
public const string AppContentsChangeStatusCancelOwn = "squidex.apps.{app}.contents.{schema}.changestatus.cancel.own";
public const string AppContentsChangeStatus = "squidex.apps.{app}.contents.{schema}.changestatus";
public const string AppContentsChangeStatusOwn = "squidex.apps.{app}.contents.{schema}.changestatus.own";
public const string AppContentsUpsert = "squidex.apps.{app}.contents.{schema}.upsert";
public const string AppContentsVersionCreate = "squidex.apps.{app}.contents.{schema}.version.create";
public const string AppContentsVersionCreateOwn = "squidex.apps.{app}.contents.{schema}.version.create.own";
public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{schema}.version.delete";
public const string AppContentsVersionDeleteOwn = "squidex.apps.{app}.contents.{schema}.version.delete.own";
public const string AppContentsDelete = "squidex.apps.{app}.contents.{schema}.delete";
public const string AppContentsDeleteOwn = "squidex.apps.{app}.contents.{schema}.delete.own";
public static Permission ForApp(string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{ {
public const string All = "squidex.*"; Guard.NotNull(id);
public const string Admin = "squidex.admin.*"; id = id.Replace("{app}", app ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{schema}", schema ?? Permission.Any, StringComparison.Ordinal);
// Admin App Creation id = id.Replace("{team}", team ?? Permission.Any, StringComparison.Ordinal);
public const string AdminAppCreate = "squidex.admin.apps.create";
// Admin Team Creation return new Permission(id);
public const string AdminTeamCreate = "squidex.admin.teams.create";
// Backup Admin
public const string AdminRestore = "squidex.admin.restore";
// Event Admin
public const string AdminEvents = "squidex.admin.events";
public const string AdminEventsRead = "squidex.admin.events.read";
public const string AdminEventsManage = "squidex.admin.events.manage";
// User Admin
public const string AdminUsers = "squidex.admin.users";
public const string AdminUsersRead = "squidex.admin.users.read";
public const string AdminUsersCreate = "squidex.admin.users.create";
public const string AdminUsersUpdate = "squidex.admin.users.update";
public const string AdminUsersUnlock = "squidex.admin.users.unlock";
public const string AdminUsersLock = "squidex.admin.users.lock";
// Team
public const string Team = "squidex.teams.{team}";
// Team Transfer
public const string Transfer = "squidex.transfer";
// Team General
public const string TeamAdmin = "squidex.teams.{team}.*";
public const string TeamUpdate = "squidex.teams.{team}.update";
// Team Contributors
public const string TeamContributors = "squidex.teams.{team}.contributors";
public const string TeamContributorsRead = "squidex.teams.{team}.contributors.read";
public const string TeamContributorsAssign = "squidex.teams.{team}.contributors.assign";
public const string TeamContributorsRevoke = "squidex.teams.{team}.contributors.revoke";
// Team Plans
public const string TeamPlans = "squidex.teams.{team}.plans";
public const string TeamPlansRead = "squidex.teams.{team}.plans.read";
public const string TeamPlansChange = "squidex.teams.{team}.plans.change";
// Team Usage
public const string TeamUsage = "squidex.teams.{team}.usage";
// Team History
public const string TeamHistory = "squidex.teams.{team}.history";
// App
public const string App = "squidex.apps.{app}";
// App
public const string AppNoScripting = "squidex.apps.{app}.no-scripting";
// App General
public const string AppAdmin = "squidex.apps.{app}.*";
public const string AppDelete = "squidex.apps.{app}.delete";
public const string AppTransfer = "squidex.apps.{app}.transfer";
public const string AppUpdate = "squidex.apps.{app}.update";
public const string AppUpdateSettings = "squidex.apps.{app}.settings";
// App Image
public const string AppImageUpload = "squidex.apps.{app}.image";
public const string AppImageDelete = "squidex.apps.{app}.image";
// App History
public const string AppHistory = "squidex.apps.{app}.history";
// App Ping
public const string AppPing = "squidex.apps.{app}.ping";
// App Search
public const string AppSearch = "squidex.apps.{app}.search";
// App Translate
public const string AppTranslate = "squidex.apps.{app}.translate";
// App Usage
public const string AppUsage = "squidex.apps.{app}.usage";
// App Comments
public const string AppComments = "squidex.apps.{app}.comments";
public const string AppCommentsRead = "squidex.apps.{app}.comments.read";
public const string AppCommentsCreate = "squidex.apps.{app}.comments.create";
public const string AppCommentsUpdate = "squidex.apps.{app}.comments.update";
public const string AppCommentsDelete = "squidex.apps.{app}.comments.delete";
// App Clients
public const string AppClients = "squidex.apps.{app}.clients";
public const string AppClientsRead = "squidex.apps.{app}.clients.read";
public const string AppClientsCreate = "squidex.apps.{app}.clients.create";
public const string AppClientsUpdate = "squidex.apps.{app}.clients.update";
public const string AppClientsDelete = "squidex.apps.{app}.clients.delete";
// App Contributors
public const string AppContributors = "squidex.apps.{app}.contributors";
public const string AppContributorsRead = "squidex.apps.{app}.contributors.read";
public const string AppContributorsAssign = "squidex.apps.{app}.contributors.assign";
public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke";
// App Languages
public const string AppLanguages = "squidex.apps.{app}.languages";
public const string AppLanguagesRead = "squidex.apps.{app}.languages.read";
public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create";
public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update";
public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete";
// App Roles
public const string AppRoles = "squidex.apps.{app}.roles";
public const string AppRolesRead = "squidex.apps.{app}.roles.read";
public const string AppRolesCreate = "squidex.apps.{app}.roles.create";
public const string AppRolesUpdate = "squidex.apps.{app}.roles.update";
public const string AppRolesDelete = "squidex.apps.{app}.roles.delete";
// App Workflows
public const string AppWorkflows = "squidex.apps.{app}.workflows";
public const string AppWorkflowsRead = "squidex.apps.{app}.workflows.read";
public const string AppWorkflowsCreate = "squidex.apps.{app}.workflows.create";
public const string AppWorkflowsUpdate = "squidex.apps.{app}.workflows.update";
public const string AppWorkflowsDelete = "squidex.apps.{app}.workflows.delete";
// App Backups
public const string AppBackups = "squidex.apps.{app}.backups";
public const string AppBackupsRead = "squidex.apps.{app}.backups.read";
public const string AppBackupsCreate = "squidex.apps.{app}.backups.create";
public const string AppBackupsDelete = "squidex.apps.{app}.backups.delete";
public const string AppBackupsDownload = "squidex.apps.{app}.backups.download";
// App Plans
public const string AppPlans = "squidex.apps.{app}.plans";
public const string AppPlansRead = "squidex.apps.{app}.plans.read";
public const string AppPlansChange = "squidex.apps.{app}.plans.change";
// App Assets
public const string AppAssets = "squidex.apps.{app}.assets";
public const string AppAssetsRead = "squidex.apps.{app}.assets.read";
public const string AppAssetsCreate = "squidex.apps.{app}.assets.create";
public const string AppAssetsUpload = "squidex.apps.{app}.assets.upload";
public const string AppAssetsUpdate = "squidex.apps.{app}.assets.update";
public const string AppAssetsDelete = "squidex.apps.{app}.assets.delete";
// App Asset Folders
public const string AppAssetFolders = "squidex.apps.{app}.assets.folders";
public const string AppAssetFoldersCreate = "squidex.apps.{app}.assets.folders.create";
public const string AppAssetFoldersUpdate = "squidex.apps.{app}.assets.folders.update";
public const string AppAssetFoldersDelete = "squidex.apps.{app}.assets.folders.delete";
// App Asset Scripts
public const string AppAssetScripts = "squidex.apps.{app}.asset-scripts";
public const string AppAssetSScriptsRead = "squidex.apps.{app}.asset-scripts.read";
public const string AppAssetsScriptsUpdate = "squidex.apps.{app}.asset-scripts.update";
// App Rules
public const string AppRules = "squidex.apps.{app}.rules";
public const string AppRulesRead = "squidex.apps.{app}.rules.read";
public const string AppRulesCreate = "squidex.apps.{app}.rules.create";
public const string AppRulesUpdate = "squidex.apps.{app}.rules.update";
public const string AppRulesDisable = "squidex.apps.{app}.rules.disable";
public const string AppRulesDelete = "squidex.apps.{app}.rules.delete";
// App Rule Events
public const string AppRulesEvents = "squidex.apps.{app}.rules.events";
public const string AppRulesEventsRun = "squidex.apps.{app}.rules.events.run";
public const string AppRulesEventsRead = "squidex.apps.{app}.rules.events.read";
public const string AppRulesEventsUpdate = "squidex.apps.{app}.rules.events.update";
public const string AppRulesEventsDelete = "squidex.apps.{app}.rules.events.delete";
// App Schemas
public const string AppSchemas = "squidex.apps.{app}.schemas";
public const string AppSchemasRead = "squidex.apps.{app}.schemas.read";
public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create";
public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{schema}.update";
public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{schema}.scripts";
public const string AppSchemasPublish = "squidex.apps.{app}.schemas.{schema}.publish";
public const string AppSchemasDelete = "squidex.apps.{app}.schemas.{schema}.delete";
// App Contents
public const string AppContents = "squidex.apps.{app}.contents.{schema}";
public const string AppContentsRead = "squidex.apps.{app}.contents.{schema}.read";
public const string AppContentsReadOwn = "squidex.apps.{app}.contents.{schema}.read.own";
public const string AppContentsCreate = "squidex.apps.{app}.contents.{schema}.create";
public const string AppContentsUpdate = "squidex.apps.{app}.contents.{schema}.update";
public const string AppContentsUpdateOwn = "squidex.apps.{app}.contents.{schema}.update.own";
public const string AppContentsChangeStatusCancel = "squidex.apps.{app}.contents.{schema}.changestatus.cancel";
public const string AppContentsChangeStatusCancelOwn = "squidex.apps.{app}.contents.{schema}.changestatus.cancel.own";
public const string AppContentsChangeStatus = "squidex.apps.{app}.contents.{schema}.changestatus";
public const string AppContentsChangeStatusOwn = "squidex.apps.{app}.contents.{schema}.changestatus.own";
public const string AppContentsUpsert = "squidex.apps.{app}.contents.{schema}.upsert";
public const string AppContentsVersionCreate = "squidex.apps.{app}.contents.{schema}.version.create";
public const string AppContentsVersionCreateOwn = "squidex.apps.{app}.contents.{schema}.version.create.own";
public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{schema}.version.delete";
public const string AppContentsVersionDeleteOwn = "squidex.apps.{app}.contents.{schema}.version.delete.own";
public const string AppContentsDelete = "squidex.apps.{app}.contents.{schema}.delete";
public const string AppContentsDeleteOwn = "squidex.apps.{app}.contents.{schema}.delete.own";
public static Permission ForApp(string id, string app = Permission.Any, string schema = Permission.Any, string team = Permission.Any)
{
Guard.NotNull(id);
id = id.Replace("{app}", app ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{schema}", schema ?? Permission.Any, StringComparison.Ordinal);
id = id.Replace("{team}", team ?? Permission.Any, StringComparison.Ordinal);
return new Permission(id);
}
} }
} }

3
backend/src/Squidex.Shared/Squidex.Shared.csproj

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>9.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

15
backend/src/Squidex.Shared/Texts.cs

@ -7,15 +7,14 @@
using System.Resources; using System.Resources;
namespace Squidex.Shared namespace Squidex.Shared;
public static class Texts
{ {
public static class Texts private static ResourceManager? resourceManager;
{
private static ResourceManager? resourceManager;
public static ResourceManager ResourceManager public static ResourceManager ResourceManager
{ {
get => resourceManager ??= new ResourceManager("Squidex.Shared.Texts", typeof(Texts).Assembly); get => resourceManager ??= new ResourceManager("Squidex.Shared.Texts", typeof(Texts).Assembly);
}
} }
} }

63
backend/src/Squidex.Shared/Users/ClientUser.cs

@ -12,46 +12,45 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
namespace Squidex.Shared.Users namespace Squidex.Shared.Users;
public sealed class ClientUser : IUser
{ {
public sealed class ClientUser : IUser private readonly RefToken token;
{ private readonly List<Claim> claims;
private readonly RefToken token;
private readonly List<Claim> claims;
public string Id public string Id
{ {
get => token.Identifier; get => token.Identifier;
} }
public string Email public string Email
{ {
get => token.ToString(); get => token.ToString();
} }
public bool IsLocked public bool IsLocked
{ {
get => false; get => false;
} }
public IReadOnlyList<Claim> Claims public IReadOnlyList<Claim> Claims
{ {
get => claims; get => claims;
} }
public object Identity => throw new NotSupportedException(); public object Identity => throw new NotSupportedException();
public ClientUser(RefToken token) public ClientUser(RefToken token)
{ {
Guard.NotNull(token); Guard.NotNull(token);
this.token = token; this.token = token;
claims = new List<Claim> claims = new List<Claim>
{ {
new Claim(OpenIdClaims.ClientId, token.Identifier), new Claim(OpenIdClaims.ClientId, token.Identifier),
new Claim(SquidexClaimTypes.DisplayName, token.Identifier) new Claim(SquidexClaimTypes.DisplayName, token.Identifier)
}; };
}
} }
} }

17
backend/src/Squidex.Shared/Users/IUser.cs

@ -8,18 +8,17 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
namespace Squidex.Shared.Users namespace Squidex.Shared.Users;
public interface IUser
{ {
public interface IUser bool IsLocked { get; }
{
bool IsLocked { get; }
string Id { get; } string Id { get; }
string Email { get; } string Email { get; }
object Identity { get; } object Identity { get; }
IReadOnlyList<Claim> Claims { get; } IReadOnlyList<Claim> Claims { get; }
}
} }

35
backend/src/Squidex.Shared/Users/IUserResolver.cs

@ -9,29 +9,28 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Shared.Users namespace Squidex.Shared.Users;
public interface IUserResolver
{ {
public interface IUserResolver Task<(IUser? User, bool Created)> CreateUserIfNotExistsAsync(string email, bool invited = false,
{ CancellationToken ct = default);
Task<(IUser? User, bool Created)> CreateUserIfNotExistsAsync(string email, bool invited = false,
CancellationToken ct = default);
Task SetClaimAsync(string id, string type, string value, bool silent = false, Task SetClaimAsync(string id, string type, string value, bool silent = false,
CancellationToken ct = default); CancellationToken ct = default);
Task<IUser?> FindByIdOrEmailAsync(string idOrEmail, Task<IUser?> FindByIdOrEmailAsync(string idOrEmail,
CancellationToken ct = default); CancellationToken ct = default);
Task<IUser?> FindByIdAsync(string idOrEmail, Task<IUser?> FindByIdAsync(string idOrEmail,
CancellationToken ct = default); CancellationToken ct = default);
Task<List<IUser>> QueryByEmailAsync(string email, Task<List<IUser>> QueryByEmailAsync(string email,
CancellationToken ct = default); CancellationToken ct = default);
Task<List<IUser>> QueryAllAsync( Task<List<IUser>> QueryAllAsync(
CancellationToken ct = default); CancellationToken ct = default);
Task<Dictionary<string, IUser>> QueryManyAsync(string[] ids, Task<Dictionary<string, IUser>> QueryManyAsync(string[] ids,
CancellationToken ct = default); CancellationToken ct = default);
}
} }

7
backend/src/Squidex.Web/IgnoreHashFileProvider.cs

@ -11,7 +11,7 @@ using Microsoft.Extensions.Primitives;
namespace Squidex.Web; namespace Squidex.Web;
public sealed class IgnoreHashFileProvider : IFileProvider public sealed partial class IgnoreHashFileProvider : IFileProvider
{ {
private readonly char[] pathSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '\\' }; private readonly char[] pathSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '\\' };
private readonly Dictionary<string, string> map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, string> map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -21,7 +21,7 @@ public sealed class IgnoreHashFileProvider : IFileProvider
{ {
this.inner = inner; this.inner = inner;
var regex = new Regex("^(?<Name>[^.]+)\\.[0-9a-f]{4,}\\.(?<Extension>.+)$"); var regex = BuildFileWithHashRegex();
void MapDirectory(string path) void MapDirectory(string path)
{ {
@ -91,4 +91,7 @@ public sealed class IgnoreHashFileProvider : IFileProvider
return $"{path1}/{path2}"; return $"{path1}/{path2}";
} }
[GeneratedRegex("^(?<Name>[^.]+)(\\.|-)[0-9A-Za-z]{4,}\\.(?<Extension>.+)$")]
private static partial Regex BuildFileWithHashRegex();
} }

2
backend/src/Squidex.Web/Squidex.Web.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/src/Squidex/Squidex.csproj

@ -3,7 +3,7 @@
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<NoWarn>NU1608</NoWarn> <NoWarn>NU1608</NoWarn>

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Entities</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Entities</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>

2
backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Domain.Users</RootNamespace> <RootNamespace>Squidex.Domain.Users</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Infrastructure</RootNamespace> <RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>

2
backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj

@ -3,7 +3,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace>Squidex.Web</RootNamespace> <RootNamespace>Squidex.Web</RootNamespace>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

2
backend/tools/GenerateLanguages/GenerateLanguages.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

13
frontend/.storybook/main.js

@ -5,6 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
const CopyPlugin = require('copy-webpack-plugin');
module.exports = { module.exports = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"], stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [ addons: [
@ -16,6 +18,17 @@ module.exports = {
name: "@storybook/angular", name: "@storybook/angular",
options: {} options: {}
}, },
webpackFinal: async config => {
/*
* Copy lazy loaded libraries to output.
*/
config.plugins.push(new CopyPlugin({
patterns: [
{ from: './node_modules/ace-builds/src-min/', to: 'dependencies/ace/' },
]
}));
return config;
},
docs: { docs: {
autodocs: true autodocs: true
} }

15
frontend/angular.json

@ -18,11 +18,11 @@
"prefix": "sqx", "prefix": "sqx",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "build", "outputPath": "build",
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "browser": "src/main.ts",
"polyfills": [ "polyfills": [
"zone.js" "zone.js"
], ],
@ -30,6 +30,7 @@
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"@tweenjs/tween.js", "@tweenjs/tween.js",
"client-only",
"copy-to-clipboard", "copy-to-clipboard",
"cropperjs", "cropperjs",
"crypto-js", "crypto-js",
@ -37,12 +38,16 @@
"crypto-js/enc-base64.js", "crypto-js/enc-base64.js",
"crypto-js/enc-utf8.js", "crypto-js/enc-utf8.js",
"crypto-js/sha256.js", "crypto-js/sha256.js",
"mousetrap", "graphql-ws",
"markdown-it",
"mersenne-twister", "mersenne-twister",
"mousetrap",
"nullthrows", "nullthrows",
"pikaday", "pikaday",
"pikaday/pikaday", "pikaday/pikaday",
"progressbar.js", "progressbar.js",
"react-dom",
"react",
"set-value", "set-value",
"slugify" "slugify"
], ],
@ -117,12 +122,10 @@
"outputHashing": "all" "outputHashing": "all"
}, },
"development": { "development": {
"buildOptimizer": false,
"extractLicenses": false, "extractLicenses": false,
"namedChunks": true, "namedChunks": true,
"optimization": false, "optimization": false,
"sourceMap": true, "sourceMap": true
"vendorChunk": true
} }
}, },
"defaultConfiguration": "production" "defaultConfiguration": "production"

383
frontend/package-lock.json

@ -8,6 +8,7 @@
"name": "squidex", "name": "squidex",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular-devkit/architect": "^0.1700.0",
"@angular/animations": "17.0.2", "@angular/animations": "17.0.2",
"@angular/cdk": "17.0.0", "@angular/cdk": "17.0.0",
"@angular/cdk-experimental": "17.0.0", "@angular/cdk-experimental": "17.0.0",
@ -30,6 +31,7 @@
"angular-gridster2": "16.0.0", "angular-gridster2": "16.0.0",
"angular-mentions": "1.5.0", "angular-mentions": "1.5.0",
"bootstrap": "5.2.3", "bootstrap": "5.2.3",
"copy-webpack-plugin": "^11.0.0",
"core-js": "3.33.2", "core-js": "3.33.2",
"cropperjs": "2.0.0-alpha.1", "cropperjs": "2.0.0-alpha.1",
"date-fns": "2.30.0", "date-fns": "2.30.0",
@ -148,59 +150,19 @@
} }
}, },
"node_modules/@angular-devkit/architect": { "node_modules/@angular-devkit/architect": {
"version": "0.1602.10", "version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.10.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-FwemQXh3edqA/S6zPpsqKei5v7gt0R0WpjJoAJaz+FOpfDwij1fwnKr88XINY8xcefTcQaTDQxJZheJShA/hHw==", "integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@angular-devkit/core": "16.2.10", "@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1" "rxjs": "7.8.1"
}, },
"engines": { "engines": {
"node": "^16.14.0 || >=18.10.0", "node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0" "yarn": ">= 1.13.0"
} }
}, },
"node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": {
"version": "16.2.10",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.10.tgz",
"integrity": "sha512-eo7suLDjyu5bSlEr4TluYkFm4v2PVLSAPgnau8XHHlN5Yg4P/BZ00ve7LA7C9S1gzRSCrxQhK5ki4rnoFTo5zg==",
"dev": true,
"peer": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "2.3.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^16.14.0 || >=18.10.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-devkit/architect/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular-devkit/build-angular": { "node_modules/@angular-devkit/build-angular": {
"version": "17.0.0", "version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.0.0.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.0.0.tgz",
@ -324,48 +286,6 @@
} }
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": {
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "3.0.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": {
"version": "7.23.2", "version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
@ -1174,15 +1094,6 @@
"ajv": "^6.9.1" "ajv": "^6.9.1"
} }
}, },
"node_modules/@angular-devkit/build-angular/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/terser": { "node_modules/@angular-devkit/build-angular/node_modules/terser": {
"version": "5.24.0", "version": "5.24.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz",
@ -1725,26 +1636,10 @@
"webpack-dev-server": "^4.0.0" "webpack-dev-server": "^4.0.0"
} }
}, },
"node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { "node_modules/@angular-devkit/core": {
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": {
"version": "17.0.0", "version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==", "integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": { "dependencies": {
"ajv": "8.12.0", "ajv": "8.12.0",
"ajv-formats": "2.1.1", "ajv-formats": "2.1.1",
@ -1767,11 +1662,10 @@
} }
} }
}, },
"node_modules/@angular-devkit/build-webpack/node_modules/picomatch": { "node_modules/@angular-devkit/core/node_modules/picomatch": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -1779,47 +1673,9 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/@angular-devkit/build-webpack/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular-devkit/core": {
"version": "16.1.7",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.7.tgz",
"integrity": "sha512-AXc9/F57Nf/A26yGu+w7PhNYriTvwazPTQsVPW/SBcTcpBa/hAsBTbPl8o8ErRJneJIoYqy/EIuabf9iiU8bRA==",
"dev": true,
"peer": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^16.14.0 || >=18.10.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-devkit/core/node_modules/source-map": { "node_modules/@angular-devkit/core/node_modules/source-map": {
"version": "0.7.4", "version": "0.7.4",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"peer": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
@ -2443,48 +2299,6 @@
"yarn": ">= 1.13.0" "yarn": ">= 1.13.0"
} }
}, },
"node_modules/@angular/cli/node_modules/@angular-devkit/architect": {
"version": "0.1700.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0.tgz",
"integrity": "sha512-whi7HvOjv1J3He9f+H8xNJWKyjAmWuWNl8gxNW6EZP/XLcrOu+/5QT4bPtXQBRIL/avZuc++5sNQS+kReaNCig==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "17.0.0",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "3.0.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": {
"version": "17.0.0", "version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0.tgz",
@ -2538,27 +2352,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@angular/cli/node_modules/picomatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@angular/cli/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@angular/common": { "node_modules/@angular/common": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.2.tgz", "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.2.tgz",
@ -6672,7 +6465,6 @@
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.9"
@ -8451,33 +8243,6 @@
"yarn": ">= 1.13.0" "yarn": ">= 1.13.0"
} }
}, },
"node_modules/@schematics/angular/node_modules/@angular-devkit/core": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0.tgz",
"integrity": "sha512-QUu3LnEi4A8t733v2+I0sLtyBJx3Q7zdTAhaauCbxbFhDid0cbYm8hYsyG/njor1irTPxSJbn6UoetVkwUQZxg==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"picomatch": "3.0.1",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": {
"version": "17.0.0", "version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0.tgz",
@ -8514,27 +8279,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@schematics/angular/node_modules/picomatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@schematics/angular/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@sigstore/bundle": { "node_modules/@sigstore/bundle": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.1.0.tgz",
@ -11066,7 +10810,6 @@
"version": "8.44.1", "version": "8.44.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.1.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.1.tgz",
"integrity": "sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==", "integrity": "sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "*", "@types/estree": "*",
"@types/json-schema": "*" "@types/json-schema": "*"
@ -11074,7 +10817,6 @@
}, },
"node_modules/@types/eslint-scope": { "node_modules/@types/eslint-scope": {
"version": "3.7.4", "version": "3.7.4",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/eslint": "*", "@types/eslint": "*",
@ -11179,8 +10921,7 @@
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.12", "version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
"dev": true
}, },
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
@ -11240,7 +10981,6 @@
"version": "20.9.0", "version": "20.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
"devOptional": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
@ -11997,7 +11737,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
"integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6" "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@ -12006,26 +11745,22 @@
"node_modules/@webassemblyjs/floating-point-hex-parser": { "node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="
"dev": true
}, },
"node_modules/@webassemblyjs/helper-api-error": { "node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
"dev": true
}, },
"node_modules/@webassemblyjs/helper-buffer": { "node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA=="
"dev": true
}, },
"node_modules/@webassemblyjs/helper-numbers": { "node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6",
@ -12035,14 +11770,12 @@
"node_modules/@webassemblyjs/helper-wasm-bytecode": { "node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
"dev": true
}, },
"node_modules/@webassemblyjs/helper-wasm-section": { "node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
"integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6",
@ -12054,7 +11787,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dev": true,
"dependencies": { "dependencies": {
"@xtuc/ieee754": "^1.2.0" "@xtuc/ieee754": "^1.2.0"
} }
@ -12063,7 +11795,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dev": true,
"dependencies": { "dependencies": {
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
} }
@ -12071,14 +11802,12 @@
"node_modules/@webassemblyjs/utf8": { "node_modules/@webassemblyjs/utf8": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
"dev": true
}, },
"node_modules/@webassemblyjs/wasm-edit": { "node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6",
@ -12094,7 +11823,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
"integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
@ -12107,7 +11835,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
"integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6",
@ -12119,7 +11846,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
"integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6",
@ -12133,7 +11859,6 @@
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
"integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
"dev": true,
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.11.6", "@webassemblyjs/ast": "1.11.6",
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
@ -12156,14 +11881,12 @@
"node_modules/@xtuc/ieee754": { "node_modules/@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
"dev": true
}, },
"node_modules/@xtuc/long": { "node_modules/@xtuc/long": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
"dev": true
}, },
"node_modules/@yarnpkg/esbuild-plugin-pnp": { "node_modules/@yarnpkg/esbuild-plugin-pnp": {
"version": "3.0.0-rc.15", "version": "3.0.0-rc.15",
@ -12306,7 +12029,6 @@
"version": "8.10.0", "version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -12318,7 +12040,6 @@
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"peerDependencies": { "peerDependencies": {
"acorn": "^8" "acorn": "^8"
} }
@ -12439,7 +12160,6 @@
"version": "8.12.0", "version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@ -12453,7 +12173,6 @@
}, },
"node_modules/ajv-formats": { "node_modules/ajv-formats": {
"version": "2.1.1", "version": "2.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ajv": "^8.0.0" "ajv": "^8.0.0"
@ -12469,7 +12188,6 @@
}, },
"node_modules/ajv-keywords": { "node_modules/ajv-keywords": {
"version": "5.1.0", "version": "5.1.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3" "fast-deep-equal": "^3.1.3"
@ -13964,7 +13682,6 @@
}, },
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/builtins": { "node_modules/builtins": {
@ -14451,7 +14168,6 @@
}, },
"node_modules/chrome-trace-event": { "node_modules/chrome-trace-event": {
"version": "1.0.3", "version": "1.0.3",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -14686,7 +14402,6 @@
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "2.20.3", "version": "2.20.3",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/common-path-prefix": { "node_modules/common-path-prefix": {
@ -14926,8 +14641,8 @@
}, },
"node_modules/copy-webpack-plugin": { "node_modules/copy-webpack-plugin": {
"version": "11.0.0", "version": "11.0.0",
"dev": true, "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz",
"license": "MIT", "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==",
"dependencies": { "dependencies": {
"fast-glob": "^3.2.11", "fast-glob": "^3.2.11",
"glob-parent": "^6.0.1", "glob-parent": "^6.0.1",
@ -14949,7 +14664,6 @@
}, },
"node_modules/copy-webpack-plugin/node_modules/glob-parent": { "node_modules/copy-webpack-plugin/node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
@ -14960,7 +14674,6 @@
}, },
"node_modules/copy-webpack-plugin/node_modules/globby": { "node_modules/copy-webpack-plugin/node_modules/globby": {
"version": "13.1.2", "version": "13.1.2",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dir-glob": "^3.0.1", "dir-glob": "^3.0.1",
@ -14978,7 +14691,6 @@
}, },
"node_modules/copy-webpack-plugin/node_modules/slash": { "node_modules/copy-webpack-plugin/node_modules/slash": {
"version": "4.0.0", "version": "4.0.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@ -15782,7 +15494,6 @@
}, },
"node_modules/dir-glob": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-type": "^4.0.0" "path-type": "^4.0.0"
@ -16302,7 +16013,6 @@
"version": "5.15.0", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
"integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
"tapable": "^2.2.0" "tapable": "^2.2.0"
@ -16486,8 +16196,7 @@
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
"dev": true
}, },
"node_modules/es-set-tostringtag": { "node_modules/es-set-tostringtag": {
"version": "2.0.1", "version": "2.0.1",
@ -17119,7 +16828,6 @@
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
@ -17424,7 +17132,6 @@
}, },
"node_modules/esrecurse": { "node_modules/esrecurse": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@ -17435,7 +17142,6 @@
}, },
"node_modules/esrecurse/node_modules/estraverse": { "node_modules/esrecurse/node_modules/estraverse": {
"version": "5.3.0", "version": "5.3.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
@ -17443,7 +17149,6 @@
}, },
"node_modules/estraverse": { "node_modules/estraverse": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
@ -17508,7 +17213,6 @@
}, },
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.8.x" "node": ">=0.8.x"
@ -17773,14 +17477,12 @@
}, },
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3", "@nodelib/fs.walk": "^1.2.3",
@ -17794,7 +17496,6 @@
}, },
"node_modules/fast-json-stable-stringify": { "node_modules/fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-levenshtein": { "node_modules/fast-levenshtein": {
@ -18626,7 +18327,6 @@
}, },
"node_modules/glob-to-regexp": { "node_modules/glob-to-regexp": {
"version": "0.4.1", "version": "0.4.1",
"dev": true,
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/global": { "node_modules/global": {
@ -18726,7 +18426,6 @@
}, },
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.10", "version": "4.2.10",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/graphemer": { "node_modules/graphemer": {
@ -19321,7 +19020,6 @@
"version": "5.2.4", "version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true,
"engines": { "engines": {
"node": ">= 4" "node": ">= 4"
} }
@ -20726,7 +20424,6 @@
}, },
"node_modules/jest-worker": { "node_modules/jest-worker": {
"version": "27.4.5", "version": "27.4.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
@ -20739,7 +20436,6 @@
}, },
"node_modules/jest-worker/node_modules/has-flag": { "node_modules/jest-worker/node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -20747,7 +20443,6 @@
}, },
"node_modules/jest-worker/node_modules/supports-color": { "node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1", "version": "8.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@ -20952,12 +20647,10 @@
}, },
"node_modules/json-parse-even-better-errors": { "node_modules/json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-stable-stringify-without-jsonify": { "node_modules/json-stable-stringify-without-jsonify": {
@ -20979,8 +20672,7 @@
"node_modules/jsonc-parser": { "node_modules/jsonc-parser": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
"dev": true
}, },
"node_modules/jsonfile": { "node_modules/jsonfile": {
"version": "6.1.0", "version": "6.1.0",
@ -21586,7 +21278,6 @@
}, },
"node_modules/loader-runner": { "node_modules/loader-runner": {
"version": "4.2.0", "version": "4.2.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.11.5" "node": ">=6.11.5"
@ -22299,7 +21990,6 @@
}, },
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/merge2": { "node_modules/merge2": {
@ -22361,7 +22051,6 @@
}, },
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.51.0", "version": "1.51.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@ -22369,7 +22058,6 @@
}, },
"node_modules/mime-types": { "node_modules/mime-types": {
"version": "2.1.34", "version": "2.1.34",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.51.0" "mime-db": "1.51.0"
@ -22815,7 +22503,6 @@
}, },
"node_modules/neo-async": { "node_modules/neo-async": {
"version": "2.6.2", "version": "2.6.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/next-tick": { "node_modules/next-tick": {
@ -24325,7 +24012,6 @@
}, },
"node_modules/path-type": { "node_modules/path-type": {
"version": "4.0.0", "version": "4.0.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -25013,7 +24699,6 @@
}, },
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.1.1", "version": "2.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -25161,7 +24846,6 @@
}, },
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
@ -25752,7 +25436,6 @@
}, },
"node_modules/require-from-string": { "node_modules/require-from-string": {
"version": "2.0.2", "version": "2.0.2",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -26100,7 +25783,6 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"dev": true,
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"ajv": "^8.9.0", "ajv": "^8.9.0",
@ -26247,7 +25929,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
"dev": true,
"dependencies": { "dependencies": {
"randombytes": "^2.1.0" "randombytes": "^2.1.0"
} }
@ -26706,7 +26387,6 @@
}, },
"node_modules/source-map-support": { "node_modules/source-map-support": {
"version": "0.5.21", "version": "0.5.21",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
@ -26715,7 +26395,6 @@
}, },
"node_modules/source-map-support/node_modules/source-map": { "node_modules/source-map-support/node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -27680,7 +27359,6 @@
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -27823,7 +27501,6 @@
"version": "5.19.2", "version": "5.19.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2", "acorn": "^8.8.2",
@ -27841,7 +27518,6 @@
"version": "5.3.9", "version": "5.3.9",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
"integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/trace-mapping": "^0.3.17", "@jridgewell/trace-mapping": "^0.3.17",
"jest-worker": "^27.4.5", "jest-worker": "^27.4.5",
@ -27873,7 +27549,6 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/ajv": { "node_modules/terser-webpack-plugin/node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@ -27888,7 +27563,6 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
"version": "3.5.2", "version": "3.5.2",
"dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"ajv": "^6.9.1" "ajv": "^6.9.1"
@ -27896,12 +27570,10 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/terser-webpack-plugin/node_modules/schema-utils": { "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "3.1.1", "version": "3.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.8", "@types/json-schema": "^7.0.8",
@ -28509,8 +28181,7 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
"devOptional": true
}, },
"node_modules/unicode-canonical-property-names-ecmascript": { "node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0", "version": "2.0.0",
@ -28727,7 +28398,6 @@
}, },
"node_modules/uri-js": { "node_modules/uri-js": {
"version": "4.4.1", "version": "4.4.1",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"punycode": "^2.1.0" "punycode": "^2.1.0"
@ -29077,7 +28747,6 @@
}, },
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.0", "version": "2.4.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
@ -29114,7 +28783,6 @@
"version": "5.88.2", "version": "5.88.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
"integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
"dev": true,
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3", "@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0", "@types/estree": "^1.0.0",
@ -29289,7 +28957,6 @@
}, },
"node_modules/webpack-sources": { "node_modules/webpack-sources": {
"version": "3.2.3", "version": "3.2.3",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10.13.0"
@ -29326,7 +28993,6 @@
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -29342,7 +29008,6 @@
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": { "peerDependencies": {
"ajv": "^6.9.1" "ajv": "^6.9.1"
} }
@ -29350,14 +29015,12 @@
"node_modules/webpack/node_modules/json-schema-traverse": { "node_modules/webpack/node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
"dev": true
}, },
"node_modules/webpack/node_modules/schema-utils": { "node_modules/webpack/node_modules/schema-utils": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.8", "@types/json-schema": "^7.0.8",
"ajv": "^6.12.5", "ajv": "^6.12.5",

2
frontend/package.json

@ -15,6 +15,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular-devkit/architect": "^0.1700.0",
"@angular/animations": "17.0.2", "@angular/animations": "17.0.2",
"@angular/cdk": "17.0.0", "@angular/cdk": "17.0.0",
"@angular/cdk-experimental": "17.0.0", "@angular/cdk-experimental": "17.0.0",
@ -37,6 +38,7 @@
"angular-gridster2": "16.0.0", "angular-gridster2": "16.0.0",
"angular-mentions": "1.5.0", "angular-mentions": "1.5.0",
"bootstrap": "5.2.3", "bootstrap": "5.2.3",
"copy-webpack-plugin": "^11.0.0",
"core-js": "3.33.2", "core-js": "3.33.2",
"cropperjs": "2.0.0-alpha.1", "cropperjs": "2.0.0-alpha.1",
"date-fns": "2.30.0", "date-fns": "2.30.0",

20
frontend/src/app/app.component.ts

@ -5,13 +5,31 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component } from '@angular/core'; import { NgIf } from '@angular/common';
import { Component, Injector } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AnalyticsService, CopyGlobalDirective, DialogRendererComponent, RootViewComponent, TourGuideComponent, TourTemplateComponent, TranslatePipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-app', selector: 'sqx-app',
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html', templateUrl: './app.component.html',
imports: [
CopyGlobalDirective,
DialogRendererComponent,
NgIf,
RootViewComponent,
RouterOutlet,
TourGuideComponent,
TourTemplateComponent,
TranslatePipe,
],
}) })
export class AppComponent { export class AppComponent {
public isLoaded?: boolean | null; public isLoaded?: boolean | null;
constructor(injector: Injector) {
injector.get(AnalyticsService);
}
} }

129
frontend/src/app/app.module.ts

@ -1,129 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ApplicationRef, DoBootstrap, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ActivatedRouteSnapshot, BaseRouteReuseStrategy, RouteReuseStrategy, RouterModule } from '@angular/router';
import { NgChartsModule } from 'ng2-charts';
import { environment } from './../environments/environment';
import { AppComponent } from './app.component';
import { routing } from './app.routes';
import { ApiUrlConfig, DateHelper, LocalizerService, SqxFrameworkModule, SqxSharedModule, TitlesConfig, UIOptions } from './shared';
import { SqxShellModule } from './shell';
const options = (window as any)['options'] || {};
DateHelper.setlocale(options.more?.culture);
function basePath() {
const baseElements = document.getElementsByTagName('base');
let baseHref: string = null!;
if (baseElements.length > 0) {
baseHref = baseElements[0].href;
}
if (baseHref.indexOf('http') === 0) {
baseHref = new URL(baseHref).pathname;
}
if (!baseHref) {
baseHref = '';
}
let path = options.embedPath || '/';
while (baseHref.endsWith('/')) {
baseHref = baseHref.substring(0, baseHref.length - 1);
}
return `${baseHref}${path}`;
}
function configApiUrl() {
const baseElements = document.getElementsByTagName('base');
let baseHref: string = null!;
if (baseElements.length > 0) {
baseHref = baseElements[0].href;
}
if (!baseHref) {
baseHref = '/';
}
if (baseHref.indexOf('http') === 0) {
return new ApiUrlConfig(baseHref);
} else {
return new ApiUrlConfig(`${window.location.protocol}//${window.location.host}${baseHref}`);
}
}
function configUIOptions() {
return new UIOptions(options);
}
function configTitles() {
return new TitlesConfig(undefined, 'i18n:common.product');
}
function configLocalizerService() {
return new LocalizerService(environment.textResolver()).logMissingKeys(environment.textLogger);
}
export class AppRouteReuseStrategy extends BaseRouteReuseStrategy {
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
return (future.routeConfig === curr.routeConfig) || (future.data['reuseId'] && future.data['reuseId'] === curr.data['reuseId']);
}
}
@NgModule({
imports: [
BrowserAnimationsModule,
BrowserModule,
CommonModule,
FormsModule,
HttpClientModule,
NgChartsModule.forRoot(),
ReactiveFormsModule,
RouterModule,
SqxFrameworkModule.forRoot(),
SqxSharedModule.forRoot(),
SqxShellModule,
routing,
],
declarations: [
AppComponent,
],
providers: [
{ provide: ApiUrlConfig, useFactory: configApiUrl },
{ provide: LocalizerService, useFactory: configLocalizerService },
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },
{ provide: TitlesConfig, useFactory: configTitles },
{ provide: UIOptions, useFactory: configUIOptions },
{ provide: APP_BASE_HREF, useValue: basePath() },
],
})
export class AppModule implements DoBootstrap {
public ngDoBootstrap(appRef: ApplicationRef) {
try {
appRef.bootstrap(AppComponent);
} catch (e) {
// eslint-disable-next-line no-console
console.log('Application element not found.');
}
}
}

43
frontend/src/app/app.routes.ts

@ -5,76 +5,75 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { ModuleWithProviders } from '@angular/core'; import { Routes } from '@angular/router';
import { RouterModule, Routes } from '@angular/router'; import { appMustExistGuard, loadAppsGuard, loadSettingsGuard, loadTeamsGuard, mustBeAuthenticatedGuard, mustBeNotAuthenticatedGuard, teamMustExistGuard, unsetAppGuard, unsetTeamGuard } from './shared';
import { AppMustExistGuard, LoadAppsGuard, LoadSettingsGuard, LoadTeamsGuard, MustBeAuthenticatedGuard, MustBeNotAuthenticatedGuard, TeamMustExistGuard, UnsetAppGuard, UnsetTeamGuard } from './shared';
import { AppAreaComponent, ForbiddenPageComponent, HomePageComponent, InternalAreaComponent, LoginPageComponent, LogoutPageComponent, NotFoundPageComponent, TeamsAreaComponent } from './shell'; import { AppAreaComponent, ForbiddenPageComponent, HomePageComponent, InternalAreaComponent, LoginPageComponent, LogoutPageComponent, NotFoundPageComponent, TeamsAreaComponent } from './shell';
const routes: Routes = [ export const APP_ROUTES: Routes = [
{ {
path: '', path: '',
component: HomePageComponent, component: HomePageComponent,
canActivate: [MustBeNotAuthenticatedGuard], canActivate: [mustBeNotAuthenticatedGuard],
}, },
{ {
path: 'app', path: 'app',
component: InternalAreaComponent, component: InternalAreaComponent,
canActivate: [MustBeAuthenticatedGuard, LoadAppsGuard, LoadTeamsGuard, LoadSettingsGuard], canActivate: [mustBeAuthenticatedGuard, loadAppsGuard, loadTeamsGuard, loadSettingsGuard],
children: [ children: [
{ {
path: '', path: '',
loadChildren: () => import('./features/apps/module').then(m => m.SqxFeatureAppsModule), loadChildren: () => import('./features/apps/routes').then(m => m.APPS_ROUTES),
canActivate: [UnsetAppGuard, UnsetTeamGuard], canActivate: [unsetAppGuard, unsetTeamGuard],
}, },
{ {
path: 'administration', path: 'administration',
loadChildren: () => import('./features/administration/module').then(m => m.SqxFeatureAdministrationModule), loadChildren: () => import('./features/administration/routes').then(m => m.ADMINISTRATION_ROUTES),
canActivate: [UnsetAppGuard, UnsetTeamGuard], canActivate: [unsetAppGuard, unsetTeamGuard],
}, },
{ {
path: 'teams', path: 'teams',
component: TeamsAreaComponent, component: TeamsAreaComponent,
canActivate: [UnsetAppGuard, UnsetTeamGuard], canActivate: [unsetAppGuard, unsetTeamGuard],
children: [ children: [
{ {
path: ':teamName', path: ':teamName',
canActivate: [TeamMustExistGuard], canActivate: [teamMustExistGuard],
loadChildren: () => import('./features/teams/module').then(m => m.SqxFeatureTeamsModule), loadChildren: () => import('./features/teams/routes').then(m => m.TEAM_ROUTES),
}, },
], ],
}, },
{ {
path: ':appName', path: ':appName',
component: AppAreaComponent, component: AppAreaComponent,
canActivate: [AppMustExistGuard], canActivate: [appMustExistGuard],
children: [ children: [
{ {
path: '', path: '',
loadChildren: () => import('./features/dashboard/module').then(m => m.SqxFeatureDashboardModule), loadChildren: () => import('./features/dashboard/routes').then(m => m.DASHBOARD_ROUTES),
}, },
{ {
path: 'content', path: 'content',
loadChildren: () => import('./features/content/module').then(m => m.SqxFeatureContentModule), loadChildren: () => import('./features/content/routes').then(m => m.CONTENT_ROUTES),
}, },
{ {
path: 'schemas', path: 'schemas',
loadChildren: () => import('./features/schemas/module').then(m => m.SqxFeatureSchemasModule), loadChildren: () => import('./features/schemas/routes').then(m => m.SCHEMAS_ROUTES),
}, },
{ {
path: 'assets', path: 'assets',
loadChildren: () => import('./features/assets/module').then(m => m.SqxFeatureAssetsModule), loadChildren: () => import('./features/assets/routes').then(m => m.ASSETS_ROUTES),
}, },
{ {
path: 'rules', path: 'rules',
loadChildren: () => import('./features/rules/module').then(m => m.SqxFeatureRulesModule), loadChildren: () => import('./features/rules/routes').then(m => m.RULES_ROUTES),
}, },
{ {
path: 'settings', path: 'settings',
loadChildren: () => import('./features/settings/module').then(m => m.SqxFeatureSettingsModule), loadChildren: () => import('./features/settings/routes').then(m => m.SETTINGS_ROUTES),
}, },
{ {
path: 'api', path: 'api',
loadChildren: () => import('./features/api/module').then(m => m.SqxFeatureApiModule), loadChildren: () => import('./features/api/routes').then(m => m.API_ROUTES),
}, },
], ],
}, },
@ -97,5 +96,3 @@ const routes: Routes = [
component: NotFoundPageComponent, component: NotFoundPageComponent,
}, },
]; ];
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { useHash: false });

22
frontend/src/app/assets/squid.svg

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
id="Layer_1" id="Layer_1"
viewBox="0 0 204.02457 346.98514" viewBox="0 0 204.02457 346.98514"
version="1.1" version="1.1"
sodipodi:docname="squid.svg" sodipodi:docname="squid.svg"
width="204.02457" width="204.02457"
height="346.98514" height="346.98514"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"> >
<defs <defs
id="defs13" /> id="defs13" />
<sodipodi:namedview <sodipodi:namedview

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

15
frontend/src/app/features/administration/administration-area.component.ts

@ -5,13 +5,26 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { UIState } from '@app/shared'; import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { LayoutContainerDirective, TitleComponent, TranslatePipe, UIState } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-administration-area', selector: 'sqx-administration-area',
styleUrls: ['./administration-area.component.scss'], styleUrls: ['./administration-area.component.scss'],
templateUrl: './administration-area.component.html', templateUrl: './administration-area.component.html',
imports: [
AsyncPipe,
LayoutContainerDirective,
NgIf,
RouterLink,
RouterLinkActive,
RouterOutlet,
TitleComponent,
TranslatePipe,
],
}) })
export class AdministrationAreaComponent { export class AdministrationAreaComponent {
constructor( constructor(

16
frontend/src/app/features/administration/declarations.ts

@ -1,16 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './administration-area.component';
export * from './guards/user-must-exist.guard';
export * from './internal';
export * from './pages/event-consumers/event-consumer.component';
export * from './pages/event-consumers/event-consumers-page.component';
export * from './pages/restore/restore-page.component';
export * from './pages/users/user-page.component';
export * from './pages/users/user.component';
export * from './pages/users/users-page.component';

44
frontend/src/app/features/administration/guards/user-must-exist.guard.spec.ts

@ -5,24 +5,36 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { firstValueFrom, of } from 'rxjs'; import { firstValueFrom, of } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq'; import { IMock, Mock, Times } from 'typemoq';
import { UserDto, UsersState } from '@app/features/administration/internal'; import { UserDto, UsersState } from '../internal';
import { UserMustExistGuard } from './user-must-exist.guard'; import { userMustExistGuard } from './user-must-exist.guard';
describe('UserMustExistGuard', () => { describe('UserMustExistGuard', () => {
let usersState: IMock<UsersState>;
let router: IMock<Router>; let router: IMock<Router>;
let userGuard: UserMustExistGuard; let usersState: IMock<UsersState>;
beforeEach(() => { beforeEach(() => {
router = Mock.ofType<Router>(); router = Mock.ofType<Router>();
usersState = Mock.ofType<UsersState>(); usersState = Mock.ofType<UsersState>();
userGuard = new UserMustExistGuard(usersState.object, router.object);
TestBed.configureTestingModule({
providers: [
{
provide: Router,
useValue: router.object,
},
{
provide: UsersState,
useValue: usersState.object,
},
],
});
}); });
it('should load user and return true if found', async () => { bit('should load user and return true if found', async () => {
usersState.setup(x => x.select('123')) usersState.setup(x => x.select('123'))
.returns(() => of(<UserDto>{})); .returns(() => of(<UserDto>{}));
@ -32,14 +44,14 @@ describe('UserMustExistGuard', () => {
}, },
}; };
const result = await firstValueFrom(userGuard.canActivate(route)); const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeTruthy(); expect(result).toBeTruthy();
usersState.verify(x => x.select('123'), Times.once()); usersState.verify(x => x.select('123'), Times.once());
}); });
it('should load user and return false if not found', async () => { bit('should load user and return false if not found', async () => {
usersState.setup(x => x.select('123')) usersState.setup(x => x.select('123'))
.returns(() => of(null)); .returns(() => of(null));
@ -49,14 +61,14 @@ describe('UserMustExistGuard', () => {
}, },
}; };
const result = await firstValueFrom(userGuard.canActivate(route)); const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeFalsy(); expect(result).toBeFalsy();
router.verify(x => x.navigate(['/404']), Times.once()); router.verify(x => x.navigate(['/404']), Times.once());
}); });
it('should unset user if user id is undefined', async () => { bit('should unset user if user id is undefined', async () => {
usersState.setup(x => x.select(null)) usersState.setup(x => x.select(null))
.returns(() => of(null)); .returns(() => of(null));
@ -66,14 +78,14 @@ describe('UserMustExistGuard', () => {
}, },
}; };
const result = await firstValueFrom(userGuard.canActivate(route)); const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeTruthy(); expect(result).toBeTruthy();
usersState.verify(x => x.select(null), Times.once()); usersState.verify(x => x.select(null), Times.once());
}); });
it('should unset user if user id is <new>', async () => { bit('should unset user if user id is <new>', async () => {
usersState.setup(x => x.select(null)) usersState.setup(x => x.select(null))
.returns(() => of(null)); .returns(() => of(null));
@ -83,10 +95,16 @@ describe('UserMustExistGuard', () => {
}, },
}; };
const result = await firstValueFrom(userGuard.canActivate(route)); const result = await firstValueFrom(userMustExistGuard(route));
expect(result).toBeTruthy(); expect(result).toBeTruthy();
usersState.verify(x => x.select(null), Times.once()); usersState.verify(x => x.select(null), Times.once());
}); });
}); });
function bit(name: string, assertion: (() => PromiseLike<any>) | (() => void)) {
it(name, () => {
return TestBed.runInInjectionContext(() => assertion());
});
}

47
frontend/src/app/features/administration/guards/user-must-exist.guard.ts

@ -5,37 +5,30 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Injectable } from '@angular/core'; import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { UsersState } from '@app/features/administration/internal'; import { allParams } from '@app/shared';
import { allParams } from '@app/framework'; import { UsersState } from '../internal';
@Injectable() export const userMustExistGuard = (route: ActivatedRouteSnapshot) => {
export class UserMustExistGuard { const usersState = inject(UsersState);
constructor(
private readonly usersState: UsersState,
private readonly router: Router,
) {
}
public canActivate(route: ActivatedRouteSnapshot): Observable<boolean> { const userId = allParams(route)['userId'];
const userId = allParams(route)['userId'];
if (!userId || userId === 'new') { if (!userId || userId === 'new') {
return this.usersState.select(null).pipe(map(u => u === null)); return usersState.select(null).pipe(map(u => u === null));
} }
const result = const router = inject(Router);
this.usersState.select(userId).pipe( const result =
tap(dto => { usersState.select(userId).pipe(
if (!dto) { tap(dto => {
this.router.navigate(['/404']); if (!dto) {
} router.navigate(['/404']);
}), }
map(u => !!u)); }),
map(u => !!u));
return result; return result;
} };
}

9
frontend/src/app/features/administration/pages/event-consumers/event-consumer.component.ts

@ -7,14 +7,21 @@
/* eslint-disable @angular-eslint/component-selector */ /* eslint-disable @angular-eslint/component-selector */
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal'; import { TooltipDirective } from '@app/shared';
import { EventConsumerDto, EventConsumersState } from '../../internal';
@Component({ @Component({
standalone: true,
selector: '[sqxEventConsumer]', selector: '[sqxEventConsumer]',
styleUrls: ['./event-consumer.component.scss'], styleUrls: ['./event-consumer.component.scss'],
templateUrl: './event-consumer.component.html', templateUrl: './event-consumer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
NgIf,
TooltipDirective,
],
}) })
export class EventConsumerComponent { export class EventConsumerComponent {
@Output() @Output()

27
frontend/src/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -5,16 +5,39 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { timer } from 'rxjs'; import { timer } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal'; import { DialogModel, LayoutComponent, ListViewComponent, ModalDialogComponent, ModalDirective, ShortcutDirective, SidebarMenuDirective, Subscriptions, SyncWidthDirective, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/shared';
import { DialogModel, Subscriptions } from '@app/shared'; import { EventConsumerDto, EventConsumersState } from '../../internal';
import { EventConsumerComponent } from './event-consumer.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-event-consumers-page', selector: 'sqx-event-consumers-page',
styleUrls: ['./event-consumers-page.component.scss'], styleUrls: ['./event-consumers-page.component.scss'],
templateUrl: './event-consumers-page.component.html', templateUrl: './event-consumers-page.component.html',
imports: [
AsyncPipe,
EventConsumerComponent,
LayoutComponent,
ListViewComponent,
ModalDialogComponent,
ModalDirective,
NgFor,
RouterLink,
RouterLinkActive,
RouterOutlet,
ShortcutDirective,
SidebarMenuDirective,
SyncWidthDirective,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
}) })
export class EventConsumersPageComponent implements OnInit { export class EventConsumersPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions(); private readonly subscriptions = new Subscriptions();

25
frontend/src/app/features/administration/pages/restore/restore-page.component.ts

@ -5,14 +5,37 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { timer } from 'rxjs'; import { timer } from 'rxjs';
import { AuthService, BackupsService, DialogService, RestoreForm, switchSafe } from '@app/shared'; import { AuthService, BackupsService, ControlErrorsComponent, DialogService, ISODatePipe, LayoutComponent, ListViewComponent, RestoreForm, SidebarMenuDirective, switchSafe, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-restore-page', selector: 'sqx-restore-page',
styleUrls: ['./restore-page.component.scss'], styleUrls: ['./restore-page.component.scss'],
templateUrl: './restore-page.component.html', templateUrl: './restore-page.component.html',
imports: [
AsyncPipe,
ControlErrorsComponent,
FormsModule,
ISODatePipe,
LayoutComponent,
ListViewComponent,
NgFor,
NgIf,
ReactiveFormsModule,
RouterLink,
RouterLinkActive,
RouterOutlet,
SidebarMenuDirective,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
}) })
export class RestorePageComponent { export class RestorePageComponent {
public restoreForm = new RestoreForm(); public restoreForm = new RestoreForm();

20
frontend/src/app/features/administration/pages/users/user-page.component.ts

@ -5,15 +5,31 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { UpsertUserDto, UserDto, UserForm, UsersState } from '@app/features/administration/internal'; import { ControlErrorsComponent, FormErrorComponent, LayoutComponent, ShortcutDirective, Subscriptions, TitleComponent, TooltipDirective, TranslatePipe } from '@app/shared';
import { Subscriptions } from '@app/shared'; import { UpsertUserDto, UserDto, UserForm, UsersState } from '../../internal';
@Component({ @Component({
standalone: true,
selector: 'sqx-user-page', selector: 'sqx-user-page',
styleUrls: ['./user-page.component.scss'], styleUrls: ['./user-page.component.scss'],
templateUrl: './user-page.component.html', templateUrl: './user-page.component.html',
imports: [
AsyncPipe,
ControlErrorsComponent,
FormErrorComponent,
FormsModule,
LayoutComponent,
NgIf,
ReactiveFormsModule,
ShortcutDirective,
TitleComponent,
TooltipDirective,
TranslatePipe,
],
}) })
export class UserPageComponent implements OnInit { export class UserPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions(); private readonly subscriptions = new Subscriptions();

15
frontend/src/app/features/administration/pages/users/user.component.ts

@ -7,14 +7,27 @@
/* eslint-disable @angular-eslint/component-selector */ /* eslint-disable @angular-eslint/component-selector */
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { UserDto, UsersState } from '@app/features/administration/internal'; import { RouterLink, RouterLinkActive } from '@angular/router';
import { ConfirmClickDirective, StopClickDirective, TooltipDirective, UserDtoPicture } from '@app/shared';
import { UserDto, UsersState } from '../../internal';
@Component({ @Component({
standalone: true,
selector: '[sqxUser]', selector: '[sqxUser]',
styleUrls: ['./user.component.scss'], styleUrls: ['./user.component.scss'],
templateUrl: './user.component.html', templateUrl: './user.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ConfirmClickDirective,
NgIf,
RouterLink,
RouterLinkActive,
StopClickDirective,
TooltipDirective,
UserDtoPicture,
],
}) })
export class UserComponent { export class UserComponent {
@Input('sqxUser') @Input('sqxUser')

31
frontend/src/app/features/administration/pages/users/users-page.component.ts

@ -5,18 +5,43 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { UntypedFormControl } from '@angular/forms'; import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { UserDto, UsersState } from '@app/features/administration/internal'; import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { Router2State, Subscriptions } from '@app/framework'; import { LayoutComponent, ListViewComponent, PagerComponent, Router2State, ShortcutDirective, SidebarMenuDirective, Subscriptions, SyncWidthDirective, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/shared';
import { UserDto, UsersState } from '../../internal';
import { UserComponent } from './user.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-users-page', selector: 'sqx-users-page',
styleUrls: ['./users-page.component.scss'], styleUrls: ['./users-page.component.scss'],
templateUrl: './users-page.component.html', templateUrl: './users-page.component.html',
providers: [ providers: [
Router2State, Router2State,
], ],
imports: [
AsyncPipe,
FormsModule,
LayoutComponent,
ListViewComponent,
NgFor,
NgIf,
PagerComponent,
ReactiveFormsModule,
RouterLink,
RouterLinkActive,
RouterOutlet,
ShortcutDirective,
SidebarMenuDirective,
SyncWidthDirective,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
UserComponent,
],
}) })
export class UsersPageComponent implements OnInit { export class UsersPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions(); private readonly subscriptions = new Subscriptions();

52
frontend/src/app/features/administration/module.ts → frontend/src/app/features/administration/routes.ts

@ -5,12 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { NgModule } from '@angular/core'; import { Routes } from '@angular/router';
import { RouterModule, Routes } from '@angular/router'; import { HelpComponent, UsersService } from '@app/shared';
import { HelpComponent, SqxFrameworkModule, SqxSharedModule } from '@app/shared'; import { AdministrationAreaComponent } from './administration-area.component';
import { AdministrationAreaComponent, EventConsumerComponent, EventConsumersPageComponent, EventConsumersService, EventConsumersState, RestorePageComponent, UserComponent, UserMustExistGuard, UserPageComponent, UsersPageComponent, UsersService, UsersState } from './declarations'; import { userMustExistGuard } from './guards/user-must-exist.guard';
import { EventConsumersService, EventConsumersState, UsersState } from './internal';
import { EventConsumersPageComponent } from './pages/event-consumers/event-consumers-page.component';
import { RestorePageComponent } from './pages/restore/restore-page.component';
import { UserPageComponent } from './pages/users/user-page.component';
import { UsersPageComponent } from './pages/users/users-page.component';
const routes: Routes = [ export const ADMINISTRATION_ROUTES: Routes = [
{ {
path: '', path: '',
component: AdministrationAreaComponent, component: AdministrationAreaComponent,
@ -23,6 +28,10 @@ const routes: Routes = [
{ {
path: 'event-consumers', path: 'event-consumers',
component: EventConsumersPageComponent, component: EventConsumersPageComponent,
providers: [
EventConsumersService,
EventConsumersState,
],
children: [ children: [
{ {
path: 'help', path: 'help',
@ -49,6 +58,10 @@ const routes: Routes = [
{ {
path: 'users', path: 'users',
component: UsersPageComponent, component: UsersPageComponent,
providers: [
UsersService,
UsersState,
],
children: [ children: [
{ {
path: 'help', path: 'help',
@ -60,35 +73,10 @@ const routes: Routes = [
{ {
path: ':userId', path: ':userId',
component: UserPageComponent, component: UserPageComponent,
canActivate: [UserMustExistGuard], canActivate: [userMustExistGuard],
}, },
], ],
}, },
], ],
}, },
]; ];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
AdministrationAreaComponent,
EventConsumerComponent,
EventConsumersPageComponent,
RestorePageComponent,
UserComponent,
UserPageComponent,
UsersPageComponent,
],
providers: [
EventConsumersService,
EventConsumersState,
UserMustExistGuard,
UsersService,
UsersState,
],
})
export class SqxFeatureAdministrationModule {}

2
frontend/src/app/features/administration/services/event-consumers.service.spec.ts

@ -7,7 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, Resource, ResourceLinks } from '@app/framework'; import { ApiUrlConfig, Resource, ResourceLinks } from '@app/shared';
import { EventConsumerDto, EventConsumersDto, EventConsumersService } from './event-consumers.service'; import { EventConsumerDto, EventConsumersDto, EventConsumersService } from './event-consumers.service';
describe('EventConsumersService', () => { describe('EventConsumersService', () => {

2
frontend/src/app/features/administration/services/users.service.spec.ts

@ -7,7 +7,7 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, Resource, ResourceLinks } from '@app/framework'; import { ApiUrlConfig, Resource, ResourceLinks } from '@app/shared';
import { UserDto, UsersDto, UsersService } from './users.service'; import { UserDto, UsersDto, UsersService } from './users.service';
describe('UsersService', () => { describe('UsersService', () => {

6
frontend/src/app/features/administration/state/event-consumers.state.spec.ts

@ -7,9 +7,9 @@
import { of, onErrorResumeNextWith, throwError } from 'rxjs'; import { of, onErrorResumeNextWith, throwError } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq'; import { IMock, It, Mock, Times } from 'typemoq';
import { EventConsumersService } from '@app/features/administration/internal'; import { DialogService } from '@app/shared';
import { DialogService } from '@app/framework'; import { EventConsumersService } from '../internal';
import { createEventConsumer } from './../services/event-consumers.service.spec'; import { createEventConsumer } from '../services/event-consumers.service.spec';
import { EventConsumersState } from './event-consumers.state'; import { EventConsumersState } from './event-consumers.state';
describe('EventConsumersState', () => { describe('EventConsumersState', () => {

2
frontend/src/app/features/administration/state/event-consumers.state.ts

@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { finalize, tap } from 'rxjs/operators'; import { finalize, tap } from 'rxjs/operators';
import { debug, DialogService, LoadingState, shareSubscribed, State } from '@app/shared'; import { debug, DialogService, LoadingState, shareSubscribed, State } from '@app/shared';
import { EventConsumerDto, EventConsumersService } from './../services/event-consumers.service'; import { EventConsumerDto, EventConsumersService } from '../services/event-consumers.service';
interface Snapshot extends LoadingState { interface Snapshot extends LoadingState {
// The list of event consumers. // The list of event consumers.

2
frontend/src/app/features/administration/state/users.forms.ts

@ -7,7 +7,7 @@
import { UntypedFormControl, Validators } from '@angular/forms'; import { UntypedFormControl, Validators } from '@angular/forms';
import { ExtendedFormGroup, Form, ValidatorsEx } from '@app/shared'; import { ExtendedFormGroup, Form, ValidatorsEx } from '@app/shared';
import { UpsertUserDto, UserDto } from './../services/users.service'; import { UpsertUserDto, UserDto } from '../services/users.service';
export class UserForm extends Form<ExtendedFormGroup, UpsertUserDto, UserDto> { export class UserForm extends Form<ExtendedFormGroup, UpsertUserDto, UserDto> {
constructor() { constructor() {

4
frontend/src/app/features/administration/state/users.state.spec.ts

@ -7,9 +7,9 @@
import { firstValueFrom, of, onErrorResumeNextWith, throwError } from 'rxjs'; import { firstValueFrom, of, onErrorResumeNextWith, throwError } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq'; import { IMock, It, Mock, Times } from 'typemoq';
import { UpsertUserDto, UsersService } from '@app/features/administration/internal';
import { DialogService } from '@app/shared'; import { DialogService } from '@app/shared';
import { createUser } from './../services/users.service.spec'; import { UpsertUserDto, UsersService } from '../internal';
import { createUser } from '../services/users.service.spec';
import { UsersState } from './users.state'; import { UsersState } from './users.state';
describe('UsersState', () => { describe('UsersState', () => {

2
frontend/src/app/features/administration/state/users.state.ts

@ -12,7 +12,7 @@ import '@app/framework/utils/rxjs-extensions';
import { EMPTY, Observable, of } from 'rxjs'; import { EMPTY, Observable, of } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs/operators'; import { catchError, finalize, tap } from 'rxjs/operators';
import { debug, DialogService, getPagingInfo, ListState, shareSubscribed, State } from '@app/shared'; import { debug, DialogService, getPagingInfo, ListState, shareSubscribed, State } from '@app/shared';
import { UpsertUserDto, UserDto, UsersService } from './../services/users.service'; import { UpsertUserDto, UserDto, UsersService } from '../services/users.service';
interface Snapshot extends ListState<string> { interface Snapshot extends ListState<string> {
// The current users. // The current users.

14
frontend/src/app/features/api/api-area.component.ts

@ -6,12 +6,24 @@
*/ */
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AppsState } from '@app/shared'; import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AppsState, ExternalLinkDirective, LayoutComponent, TitleComponent, TourStepDirective, TranslatePipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-api-area', selector: 'sqx-api-area',
styleUrls: ['./api-area.component.scss'], styleUrls: ['./api-area.component.scss'],
templateUrl: './api-area.component.html', templateUrl: './api-area.component.html',
imports: [
ExternalLinkDirective,
LayoutComponent,
RouterLink,
RouterLinkActive,
RouterOutlet,
TitleComponent,
TourStepDirective,
TranslatePipe,
],
}) })
export class ApiAreaComponent { export class ApiAreaComponent {
constructor( constructor(

9
frontend/src/app/features/api/declarations.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './api-area.component';
export * from './pages/graphql/graphql-page.component';

9
frontend/src/app/features/api/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

37
frontend/src/app/features/api/module.ts

@ -1,37 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { ApiAreaComponent, GraphQLPageComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: ApiAreaComponent,
children: [
{
path: 'graphql',
component: GraphQLPageComponent,
},
],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
ApiAreaComponent,
GraphQLPageComponent,
],
})
export class SqxFeatureApiModule {}

19
frontend/src/app/features/api/pages/graphql/graphql-page.component.ts

@ -5,17 +5,34 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { createGraphiQLFetcher } from '@graphiql/toolkit'; import { createGraphiQLFetcher } from '@graphiql/toolkit';
import GraphiQL from 'graphiql'; import GraphiQL from 'graphiql';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { ApiUrlConfig, AppsState, AuthService, ClientDto, ClientsService, ClientsState, DialogModel, MessageBus, QueryExecuted, Types } from '@app/shared'; import { ApiUrlConfig, AppsState, AuthService, ClientDto, ClientsService, ClientsState, DialogModel, FormHintComponent, LayoutComponent, MessageBus, ModalDialogComponent, ModalDirective, QueryExecuted, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe, Types } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-graphql-page', selector: 'sqx-graphql-page',
styleUrls: ['./graphql-page.component.scss'], styleUrls: ['./graphql-page.component.scss'],
templateUrl: './graphql-page.component.html', templateUrl: './graphql-page.component.html',
imports: [
AsyncPipe,
FormHintComponent,
FormsModule,
LayoutComponent,
ModalDialogComponent,
ModalDirective,
NgFor,
NgIf,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
}) })
export class GraphQLPageComponent implements AfterViewInit, OnInit { export class GraphQLPageComponent implements AfterViewInit, OnInit {
@ViewChild('graphiQLContainer', { static: false }) @ViewChild('graphiQLContainer', { static: false })

23
frontend/src/app/features/api/routes.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Routes } from '@angular/router';
import { ApiAreaComponent } from './api-area.component';
import { GraphQLPageComponent } from './pages/graphql/graphql-page.component';
export const API_ROUTES: Routes = [
{
path: '',
component: ApiAreaComponent,
children: [
{
path: 'graphql',
component: GraphQLPageComponent,
},
],
},
];

12
frontend/src/app/features/apps/declarations.ts

@ -1,12 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/app.component';
export * from './pages/apps-page.component';
export * from './pages/news-dialog.component';
export * from './pages/onboarding-dialog.component';
export * from './pages/team.component';

9
frontend/src/app/features/apps/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

34
frontend/src/app/features/apps/module.ts

@ -1,34 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { AppComponent, AppsPageComponent, NewsDialogComponent, OnboardingDialogComponent, TeamComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: AppsPageComponent,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
AppComponent,
AppsPageComponent,
NewsDialogComponent,
OnboardingDialogComponent,
TeamComponent,
],
})
export class SqxFeatureAppsModule {}

17
frontend/src/app/features/apps/pages/app.component.ts

@ -5,14 +5,29 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppDto, ModalModel } from '@app/shared'; import { RouterLink } from '@angular/router';
import { AppDto, AvatarComponent, ConfirmClickDirective, DropdownMenuComponent, ModalDirective, ModalModel, ModalPlacementDirective, StopClickDirective, TourStepDirective, TranslatePipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-app', selector: 'sqx-app',
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html', templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AvatarComponent,
ConfirmClickDirective,
DropdownMenuComponent,
ModalDirective,
ModalPlacementDirective,
NgIf,
RouterLink,
StopClickDirective,
TourStepDirective,
TranslatePipe,
],
}) })
export class AppComponent { export class AppComponent {
@Input({ required: true }) @Input({ required: true })

24
frontend/src/app/features/apps/pages/apps-page.component.ts

@ -5,18 +5,38 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { combineLatest } from 'rxjs'; import { combineLatest } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { AppDto, AppsState, AuthService, DialogModel, FeatureDto, LocalStoreService, NewsService, TeamDto, TeamsState, TemplateDto, TemplatesState, TourState, UIOptions, UIState } from '@app/shared'; import { AppDto, AppFormComponent, AppsState, AuthService, DialogModel, FeatureDto, FormHintComponent, LocalStoreService, ModalDirective, NewsService, Settings, TeamDto, TeamsState, TemplateDto, TemplatesState, TitleComponent, TourState, TourStepDirective, TranslatePipe, UIOptions, UIState } from '@app/shared';
import { Settings } from '@app/shared/state/settings'; import { AppComponent } from './app.component';
import { NewsDialogComponent } from './news-dialog.component';
import { OnboardingDialogComponent } from './onboarding-dialog.component';
import { TeamComponent } from './team.component';
type GroupedApps = { team?: TeamDto; apps: AppDto[] }; type GroupedApps = { team?: TeamDto; apps: AppDto[] };
@Component({ @Component({
standalone: true,
selector: 'sqx-apps-page', selector: 'sqx-apps-page',
styleUrls: ['./apps-page.component.scss'], styleUrls: ['./apps-page.component.scss'],
templateUrl: './apps-page.component.html', templateUrl: './apps-page.component.html',
imports: [
AppComponent,
AppFormComponent,
AsyncPipe,
FormHintComponent,
ModalDirective,
NewsDialogComponent,
NgFor,
NgIf,
OnboardingDialogComponent,
TeamComponent,
TitleComponent,
TourStepDirective,
TranslatePipe,
],
}) })
export class AppsPageComponent implements OnInit { export class AppsPageComponent implements OnInit {
public addAppDialog = new DialogModel(); public addAppDialog = new DialogModel();

11
frontend/src/app/features/apps/pages/news-dialog.component.ts

@ -5,13 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { NgFor } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FeatureDto } from '@app/shared'; import { FeatureDto, HelpMarkdownPipe, ModalDialogComponent, TooltipDirective, TranslatePipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-news-dialog', selector: 'sqx-news-dialog',
styleUrls: ['./news-dialog.component.scss'], styleUrls: ['./news-dialog.component.scss'],
templateUrl: './news-dialog.component.html', templateUrl: './news-dialog.component.html',
imports: [
HelpMarkdownPipe,
ModalDialogComponent,
NgFor,
TooltipDirective,
TranslatePipe,
],
}) })
export class NewsDialogComponent { export class NewsDialogComponent {
@Output() @Output()

16
frontend/src/app/features/apps/pages/onboarding-dialog.component.ts

@ -5,18 +5,28 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { NgIf } from '@angular/common';
import { Component, EventEmitter, Output } from '@angular/core'; import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { fadeAnimation, slideAnimation } from '@app/framework'; import { fadeAnimation, MarkdownPipe, ModalDialogComponent, SafeHtmlPipe, slideAnimation, TourState, TranslatePipe, UsersService } from '@app/shared';
import { TourState, UsersService } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-onboarding-dialog', selector: 'sqx-onboarding-dialog',
styleUrls: ['./onboarding-dialog.component.scss'], styleUrls: ['./onboarding-dialog.component.scss'],
templateUrl: './onboarding-dialog.component.html', templateUrl: './onboarding-dialog.component.html',
animations: [ animations: [
fadeAnimation, slideAnimation, fadeAnimation, slideAnimation,
], ],
imports: [
FormsModule,
MarkdownPipe,
ModalDialogComponent,
NgIf,
ReactiveFormsModule,
SafeHtmlPipe,
TranslatePipe,
],
}) })
export class OnboardingDialogComponent { export class OnboardingDialogComponent {

13
frontend/src/app/features/apps/pages/team.component.ts

@ -6,13 +6,24 @@
*/ */
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ModalModel, TeamDto } from '@app/shared'; import { RouterLink } from '@angular/router';
import { ConfirmClickDirective, DropdownMenuComponent, ModalDirective, ModalModel, ModalPlacementDirective, StopClickDirective, TeamDto, TranslatePipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-team', selector: 'sqx-team',
styleUrls: ['./team.component.scss'], styleUrls: ['./team.component.scss'],
templateUrl: './team.component.html', templateUrl: './team.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ConfirmClickDirective,
DropdownMenuComponent,
ModalDirective,
ModalPlacementDirective,
RouterLink,
StopClickDirective,
TranslatePipe,
],
}) })
export class TeamComponent { export class TeamComponent {
@Input({ required: true }) @Input({ required: true })

16
frontend/src/app/features/apps/routes.ts

@ -0,0 +1,16 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Routes } from '@angular/router';
import { AppsPageComponent } from './pages/apps-page.component';
export const APPS_ROUTES: Routes = [
{
path: '',
component: AppsPageComponent,
},
];

11
frontend/src/app/features/assets/declarations.ts

@ -1,11 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/asset-tag-dialog.component';
export * from './pages/asset-tags.component';
export * from './pages/assets-filters-page.component';
export * from './pages/assets-page.component';

9
frontend/src/app/features/assets/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

39
frontend/src/app/features/assets/module.ts

@ -1,39 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { AssetsFiltersPageComponent, AssetsPageComponent, AssetTagDialogComponent, AssetTagsComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: AssetsPageComponent,
children: [
{
path: 'filters',
component: AssetsFiltersPageComponent,
},
],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
],
declarations: [
AssetsFiltersPageComponent,
AssetsPageComponent,
AssetTagDialogComponent,
AssetTagsComponent,
],
})
export class SqxFeatureAssetsModule {}

15
frontend/src/app/features/assets/pages/asset-tag-dialog.component.ts

@ -5,13 +5,28 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ControlErrorsComponent, FocusOnInitDirective, FormErrorComponent, ModalDialogComponent, TooltipDirective, TranslatePipe } from '@app/shared';
import { AssetsState, RenameAssetTagForm } from '@app/shared/internal'; import { AssetsState, RenameAssetTagForm } from '@app/shared/internal';
@Component({ @Component({
standalone: true,
selector: 'sqx-asset-tag-dialog', selector: 'sqx-asset-tag-dialog',
styleUrls: ['./asset-tag-dialog.component.scss'], styleUrls: ['./asset-tag-dialog.component.scss'],
templateUrl: './asset-tag-dialog.component.html', templateUrl: './asset-tag-dialog.component.html',
imports: [
AsyncPipe,
ControlErrorsComponent,
FocusOnInitDirective,
FormErrorComponent,
FormsModule,
ModalDialogComponent,
ReactiveFormsModule,
TooltipDirective,
TranslatePipe,
],
}) })
export class AssetTagDialogComponent implements OnInit { export class AssetTagDialogComponent implements OnInit {
@Output() @Output()

13
frontend/src/app/features/assets/pages/asset-tags.component.ts

@ -7,14 +7,25 @@
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
import { NgFor, NgIf } from '@angular/common';
import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { DialogModel, TagItem, TagsSelected } from '@app/shared'; import { DialogModel, ModalDirective, StopClickDirective, TagItem, TagsSelected, TranslatePipe } from '@app/shared';
import { AssetTagDialogComponent } from './asset-tag-dialog.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-asset-tags', selector: 'sqx-asset-tags',
styleUrls: ['./asset-tags.component.scss'], styleUrls: ['./asset-tags.component.scss'],
templateUrl: './asset-tags.component.html', templateUrl: './asset-tags.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AssetTagDialogComponent,
ModalDirective,
NgFor,
NgIf,
StopClickDirective,
TranslatePipe,
],
}) })
export class AssetTagsComponent { export class AssetTagsComponent {
@Output() @Output()

12
frontend/src/app/features/assets/pages/assets-filters-page.component.ts

@ -5,13 +5,23 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AssetsState, Queries, Query, UIState } from '@app/shared'; import { AssetsState, LayoutComponent, Queries, Query, SavedQueriesComponent, TranslatePipe, UIState } from '@app/shared';
import { AssetTagsComponent } from './asset-tags.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-assets-filters-page', selector: 'sqx-assets-filters-page',
styleUrls: ['./assets-filters-page.component.scss'], styleUrls: ['./assets-filters-page.component.scss'],
templateUrl: './assets-filters-page.component.html', templateUrl: './assets-filters-page.component.html',
imports: [
AssetTagsComponent,
AsyncPipe,
LayoutComponent,
SavedQueriesComponent,
TranslatePipe,
],
}) })
export class AssetsFiltersPageComponent { export class AssetsFiltersPageComponent {
public assetsQueries: Queries; public assetsQueries: Queries;

31
frontend/src/app/features/assets/pages/assets-page.component.ts

@ -5,17 +5,44 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AssetDto, AssetsState, DialogModel, LocalStoreService, MathHelper, Queries, Query, QueryFullTextSynchronizer, Router2State, UIState } from '@app/shared'; import { FormsModule } from '@angular/forms';
import { Settings } from '@app/shared/state/settings'; import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AssetDialogComponent, AssetDto, AssetFolderDialogComponent, AssetPathComponent, AssetsListComponent, AssetsState, DialogModel, LayoutComponent, ListViewComponent, LocalStoreService, MathHelper, ModalDirective, PagerComponent, Queries, Query, QueryFullTextSynchronizer, Router2State, SearchFormComponent, Settings, ShortcutDirective, SidebarMenuDirective, TagEditorComponent, TitleComponent, TooltipDirective, TourStepDirective, TranslatePipe, UIState } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-assets-page', selector: 'sqx-assets-page',
styleUrls: ['./assets-page.component.scss'], styleUrls: ['./assets-page.component.scss'],
templateUrl: './assets-page.component.html', templateUrl: './assets-page.component.html',
providers: [ providers: [
Router2State, Router2State,
], ],
imports: [
AssetDialogComponent,
AssetFolderDialogComponent,
AssetPathComponent,
AssetsListComponent,
AsyncPipe,
FormsModule,
LayoutComponent,
ListViewComponent,
ModalDirective,
NgIf,
PagerComponent,
RouterLink,
RouterLinkActive,
RouterOutlet,
SearchFormComponent,
ShortcutDirective,
SidebarMenuDirective,
TagEditorComponent,
TitleComponent,
TooltipDirective,
TourStepDirective,
TranslatePipe,
],
}) })
export class AssetsPageComponent implements OnInit { export class AssetsPageComponent implements OnInit {
public editAsset?: AssetDto; public editAsset?: AssetDto;

23
frontend/src/app/features/assets/routes.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Routes } from '@angular/router';
import { AssetsFiltersPageComponent } from './pages/assets-filters-page.component';
import { AssetsPageComponent } from './pages/assets-page.component';
export const ASSETS_ROUTES: Routes = [
{
path: '',
component: AssetsPageComponent,
children: [
{
path: 'filters',
component: AssetsFiltersPageComponent,
},
],
},
];

43
frontend/src/app/features/content/declarations.ts

@ -1,43 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/calendar/calendar-page.component';
export * from './pages/comments/comments-page.component';
export * from './pages/content/content-event.component';
export * from './pages/content/content-history-page.component';
export * from './pages/content/content-page.component';
export * from './pages/content/editor/content-editor.component';
export * from './pages/content/editor/content-field.component';
export * from './pages/content/editor/content-section.component';
export * from './pages/content/editor/field-copy-button.component';
export * from './pages/content/editor/field-languages.component';
export * from './pages/content/inspecting/content-inspection.component';
export * from './pages/content/references/content-references.component';
export * from './pages/contents/contents-filters-page.component';
export * from './pages/contents/contents-page.component';
export * from './pages/contents/custom-view-editor.component';
export * from './pages/schemas/schemas-page.component';
export * from './pages/sidebar/sidebar-page.component';
export * from './pages/references/references-page.component';
export * from './shared/content-extension.component';
export * from './shared/due-time-selector.component';
export * from './shared/forms/array-editor.component';
export * from './shared/forms/array-item.component';
export * from './shared/forms/assets-editor.component';
export * from './shared/forms/component-section.component';
export * from './shared/forms/component.component';
export * from './shared/forms/field-editor.component';
export * from './shared/forms/iframe-editor.component';
export * from './shared/forms/stock-photo-editor.component';
export * from './shared/list/content.component';
export * from './shared/preview-button.component';
export * from './shared/references/content-creator.component';
export * from './shared/references/reference-dropdown.component';
export * from './shared/references/reference-item.component';
export * from './shared/references/references-checkboxes.component';
export * from './shared/references/references-editor.component';
export * from './shared/references/references-tags.component';

9
frontend/src/app/features/content/index.ts

@ -1,9 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './declarations';
export * from './module';

135
frontend/src/app/features/content/module.ts

@ -1,135 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { VirtualScrollerModule } from '@iharbeck/ngx-virtual-scroller';
import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, LoadSchemasGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CalendarPageComponent, CommentsPageComponent, ComponentComponent, ComponentSectionComponent, ContentComponent, ContentCreatorComponent, ContentEditorComponent, ContentEventComponent, ContentExtensionComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentInspectionComponent, ContentPageComponent, ContentReferencesComponent, ContentSectionComponent, ContentsFiltersPageComponent, ContentsPageComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldCopyButtonComponent, FieldEditorComponent, FieldLanguagesComponent, IFrameEditorComponent, PreviewButtonComponent, ReferenceDropdownComponent, ReferenceItemComponent, ReferencesCheckboxesComponent, ReferencesEditorComponent, ReferencesPageComponent, ReferencesTagsComponent, SchemasPageComponent, SidebarPageComponent, StockPhotoEditorComponent } from './declarations';
const routes: Routes = [
{
path: '',
component: SchemasPageComponent,
canActivate: [LoadLanguagesGuard, LoadSchemasGuard],
children: [
{
path: '__calendar',
component: CalendarPageComponent,
},
{
path: '__references/:referenceId',
component: ReferencesPageComponent,
},
{
path: ':schemaName',
canActivate: [SchemaMustExistPublishedGuard],
children: [
{
path: '',
component: ContentsPageComponent,
canActivate: [SchemaMustNotBeSingletonGuard, ContentMustExistGuard],
canDeactivate: [CanDeactivateGuard],
children: [
{
path: 'filters',
component: ContentsFiltersPageComponent,
},
{
path: 'sidebar',
component: SidebarPageComponent,
},
],
},
{
path: 'new',
component: ContentPageComponent,
canActivate: [SchemaMustNotBeSingletonGuard, ContentMustExistGuard],
canDeactivate: [CanDeactivateGuard],
data: {
reuseId: 'contentPage',
},
},
{
path: ':contentId',
component: ContentPageComponent,
canActivate: [ContentMustExistGuard],
canDeactivate: [CanDeactivateGuard],
data: {
reuseId: 'contentPage',
},
children: [
{
path: 'history',
component: ContentHistoryPageComponent,
data: {
channel: 'contents.{contentId}',
},
},
{
path: 'comments',
component: CommentsPageComponent,
},
{
path: 'sidebar',
component: SidebarPageComponent,
},
],
},
],
}],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
SqxFrameworkModule,
SqxSharedModule,
VirtualScrollerModule,
],
declarations: [
ArrayEditorComponent,
ArrayItemComponent,
AssetsEditorComponent,
CalendarPageComponent,
CommentsPageComponent,
ComponentComponent,
ComponentSectionComponent,
ContentComponent,
ContentCreatorComponent,
ContentEditorComponent,
ContentEventComponent,
ContentExtensionComponent,
ContentFieldComponent,
ContentHistoryPageComponent,
ContentInspectionComponent,
ContentPageComponent,
ContentReferencesComponent,
ContentSectionComponent,
ContentsFiltersPageComponent,
ContentsPageComponent,
CustomViewEditorComponent,
DueTimeSelectorComponent,
FieldCopyButtonComponent,
FieldEditorComponent,
FieldLanguagesComponent,
IFrameEditorComponent,
PreviewButtonComponent,
ReferenceDropdownComponent,
ReferenceItemComponent,
ReferencesCheckboxesComponent,
ReferencesEditorComponent,
ReferencesEditorComponent,
ReferencesPageComponent,
ReferencesTagsComponent,
SchemasPageComponent,
SidebarPageComponent,
StockPhotoEditorComponent,
],
})
export class SqxFeatureContentModule {}

23
frontend/src/app/features/content/pages/calendar/calendar-page.component.ts

@ -5,17 +5,38 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { NgIf } from '@angular/common';
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AppsState, ContentDto, ContentsService, DateTime, DialogModel, getContentValue, LanguageDto, LanguagesState, LocalizerService, ResourceLoaderService } from '@app/shared'; import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { AppsState, ConfirmClickDirective, ContentDto, ContentsService, ContentStatusComponent, CopyDirective, DateTime, DialogModel, FullDateTimePipe, getContentValue, LanguageDto, LanguagesState, LayoutComponent, LocalizerService, ModalDialogComponent, ModalDirective, ResourceLoaderService, TitleComponent, TooltipDirective, TranslatePipe, UserNameRefPipe, UserPictureRefPipe } from '@app/shared';
declare const tui: any; declare const tui: any;
type ViewMode = 'day' | 'week' | 'month'; type ViewMode = 'day' | 'week' | 'month';
@Component({ @Component({
standalone: true,
selector: 'sqx-calendar-page', selector: 'sqx-calendar-page',
styleUrls: ['./calendar-page.component.scss'], styleUrls: ['./calendar-page.component.scss'],
templateUrl: './calendar-page.component.html', templateUrl: './calendar-page.component.html',
imports: [
ConfirmClickDirective,
ContentStatusComponent,
CopyDirective,
FormsModule,
FullDateTimePipe,
LayoutComponent,
ModalDialogComponent,
ModalDirective,
NgIf,
RouterLink,
TitleComponent,
TooltipDirective,
TranslatePipe,
UserNameRefPipe,
UserPictureRefPipe,
],
}) })
export class CalendarPageComponent implements AfterViewInit, OnDestroy, OnInit { export class CalendarPageComponent implements AfterViewInit, OnDestroy, OnInit {
private calendar: any; private calendar: any;

8
frontend/src/app/features/content/pages/comments/comments-page.component.ts

@ -5,14 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { CommentsComponent, LayoutComponent } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-comments-page', selector: 'sqx-comments-page',
styleUrls: ['./comments-page.component.scss'], styleUrls: ['./comments-page.component.scss'],
templateUrl: './comments-page.component.html', templateUrl: './comments-page.component.html',
imports: [
AsyncPipe,
CommentsComponent,
LayoutComponent,
],
}) })
export class CommentsPageComponent { export class CommentsPageComponent {
public commentsId = this.route.parent!.params.pipe(map(x => x['contentId'])); public commentsId = this.route.parent!.params.pipe(map(x => x['contentId']));

13
frontend/src/app/features/content/pages/content/content-event.component.ts

@ -5,14 +5,25 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ContentDto, HistoryEventDto, TypedSimpleChanges } from '@app/shared'; import { ContentDto, FromNowPipe, HistoryEventDto, HistoryMessagePipe, TooltipDirective, TranslatePipe, TypedSimpleChanges, UserNameRefPipe, UserPictureRefPipe } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-content-event', selector: 'sqx-content-event',
styleUrls: ['./content-event.component.scss'], styleUrls: ['./content-event.component.scss'],
templateUrl: './content-event.component.html', templateUrl: './content-event.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
FromNowPipe,
HistoryMessagePipe,
NgIf,
TooltipDirective,
TranslatePipe,
UserNameRefPipe,
UserPictureRefPipe,
],
}) })
export class ContentEventComponent { export class ContentEventComponent {
@Output() @Output()

25
frontend/src/app/features/content/pages/content/content-history-page.component.ts

@ -5,17 +5,38 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { Observable, timer } from 'rxjs'; import { Observable, timer } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { AppsState, ContentDto, ContentsState, defined, HistoryEventDto, HistoryService, ModalModel, SchemasState, Subscriptions, switchSafe } from '@app/shared'; import { AppsState, ConfirmClickDirective, ContentDto, ContentsState, ContentStatusComponent, CopyDirective, defined, DropdownMenuComponent, FormHintComponent, FromNowPipe, HistoryEventDto, HistoryService, LayoutComponent, ModalDirective, ModalModel, ModalPlacementDirective, SchemasState, Subscriptions, switchSafe, TourStepDirective, TranslatePipe } from '@app/shared';
import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component'; import { DueTimeSelectorComponent } from '../../shared/due-time-selector.component';
import { ContentEventComponent } from './content-event.component';
import { ContentPageComponent } from './content-page.component'; import { ContentPageComponent } from './content-page.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-history', selector: 'sqx-history',
styleUrls: ['./content-history-page.component.scss'], styleUrls: ['./content-history-page.component.scss'],
templateUrl: './content-history-page.component.html', templateUrl: './content-history-page.component.html',
imports: [
AsyncPipe,
ConfirmClickDirective,
ContentEventComponent,
ContentStatusComponent,
CopyDirective,
DropdownMenuComponent,
DueTimeSelectorComponent,
FormHintComponent,
FromNowPipe,
LayoutComponent,
ModalDirective,
ModalPlacementDirective,
NgFor,
NgIf,
TourStepDirective,
TranslatePipe,
],
}) })
export class ContentHistoryPageComponent implements OnInit { export class ContentHistoryPageComponent implements OnInit {
private readonly subscriptions = new Subscriptions(); private readonly subscriptions = new Subscriptions();

44
frontend/src/app/features/content/pages/content/content-page.component.ts

@ -5,13 +5,21 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgIf, NgSwitch, NgSwitchCase } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators'; import { filter, map, tap } from 'rxjs/operators';
import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, CollaborationService, ContentDto, ContentsState, defined, DialogService, EditContentForm, LanguagesState, LocalStoreService, ModalModel, ResolveAssets, ResolveContents, SchemaDto, SchemasState, Settings, Subscriptions, TempService, ToolbarService, Types, Version } from '@app/shared'; import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, CollaborationService, ConfirmClickDirective, ContentDto, ContentsState, defined, DialogService, DropdownMenuComponent, EditContentForm, LanguageSelectorComponent, LanguagesState, LayoutComponent, LocalStoreService, ModalDirective, ModalModel, ModalPlacementDirective, NotifoComponent, ResolveAssets, ResolveContents, SchemaDto, SchemasState, Settings, ShortcutDirective, SidebarMenuDirective, Subscriptions, TempService, TitleComponent, ToolbarComponent, ToolbarService, TooltipDirective, TourHintDirective, TourStepDirective, TranslatePipe, Types, Version, WatchingUsersComponent } from '@app/shared';
import { ContentExtensionComponent } from '../../shared/content-extension.component';
import { PreviewButtonComponent } from '../../shared/preview-button.component';
import { ContentEditorComponent } from './editor/content-editor.component';
import { ContentInspectionComponent } from './inspecting/content-inspection.component';
import { ContentReferencesComponent } from './references/content-references.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-content-page', selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'], styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html', templateUrl: './content-page.component.html',
@ -21,6 +29,38 @@ import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, Auto
ResolveContents, ResolveContents,
ToolbarService, ToolbarService,
], ],
imports: [
AsyncPipe,
ConfirmClickDirective,
ContentEditorComponent,
ContentExtensionComponent,
ContentInspectionComponent,
ContentReferencesComponent,
DropdownMenuComponent,
FormsModule,
LanguageSelectorComponent,
LayoutComponent,
ModalDirective,
ModalPlacementDirective,
NgIf,
NgSwitch,
NgSwitchCase,
NotifoComponent,
PreviewButtonComponent,
ReactiveFormsModule,
RouterLink,
RouterLinkActive,
RouterOutlet,
ShortcutDirective,
SidebarMenuDirective,
TitleComponent,
ToolbarComponent,
TooltipDirective,
TourHintDirective,
TourStepDirective,
TranslatePipe,
WatchingUsersComponent,
],
}) })
export class ContentPageComponent implements CanComponentDeactivate, OnInit { export class ContentPageComponent implements CanComponentDeactivate, OnInit {
private readonly subscriptions = new Subscriptions(); private readonly subscriptions = new Subscriptions();

20
frontend/src/app/features/content/pages/content/editor/content-editor.component.ts

@ -5,13 +5,31 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core'; import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppLanguageDto, EditContentForm, FieldForm, FieldSection, RootFieldDto, SchemaDto, Version } from '@app/shared'; import { FormsModule } from '@angular/forms';
import { AppLanguageDto, CursorsComponent, CursorsDirective, EditContentForm, FieldForm, FieldSection, FormErrorComponent, ListViewComponent, MarkdownInlinePipe, RootFieldDto, SafeHtmlPipe, SchemaDto, TranslatePipe, Version } from '@app/shared';
import { ContentSectionComponent } from '../../../shared/forms/content-section.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-content-editor', selector: 'sqx-content-editor',
styleUrls: ['./content-editor.component.scss'], styleUrls: ['./content-editor.component.scss'],
templateUrl: './content-editor.component.html', templateUrl: './content-editor.component.html',
imports: [
AsyncPipe,
ContentSectionComponent,
CursorsComponent,
CursorsDirective,
FormErrorComponent,
FormsModule,
ListViewComponent,
MarkdownInlinePipe,
NgFor,
NgIf,
SafeHtmlPipe,
TranslatePipe,
],
}) })
export class ContentEditorComponent { export class ContentEditorComponent {
@Output() @Output()

13
frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts

@ -5,17 +5,28 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BehaviorSubject, combineLatest, of } from 'rxjs'; import { BehaviorSubject, combineLatest, of } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators'; import { filter, map, switchMap } from 'rxjs/operators';
import { AppLanguageDto, ContentDto, ContentsService, ContentsState, ErrorDto, ToolbarService, TypedSimpleChanges } from '@app/shared'; import { AppLanguageDto, CodeEditorComponent, ContentDto, ContentsService, ContentsState, ErrorDto, FormErrorComponent, ToolbarService, TranslatePipe, TypedSimpleChanges } from '@app/shared';
type Mode = 'Content' | 'Data' | 'FlatData'; type Mode = 'Content' | 'Data' | 'FlatData';
@Component({ @Component({
standalone: true,
selector: 'sqx-content-inspection', selector: 'sqx-content-inspection',
styleUrls: ['./content-inspection.component.scss'], styleUrls: ['./content-inspection.component.scss'],
templateUrl: './content-inspection.component.html', templateUrl: './content-inspection.component.html',
imports: [
AsyncPipe,
CodeEditorComponent,
FormErrorComponent,
FormsModule,
NgIf,
TranslatePipe,
],
}) })
export class ContentInspectionComponent implements OnDestroy { export class ContentInspectionComponent implements OnDestroy {
private languageChanges$ = new BehaviorSubject<AppLanguageDto | null>(null); private languageChanges$ = new BehaviorSubject<AppLanguageDto | null>(null);

18
frontend/src/app/features/content/pages/content/references/content-references.component.ts

@ -5,16 +5,30 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { AppLanguageDto, ComponentContentsState, ContentDto, QuerySynchronizer, Router2State, ToolbarService, TypedSimpleChanges } from '@app/shared'; import { AppLanguageDto, ComponentContentsState, ContentDto, ContentsColumnsPipe, ListViewComponent, PagerComponent, QuerySynchronizer, Router2State, ToolbarService, TranslatePipe, TypedSimpleChanges } from '@app/shared';
import { ReferenceItemComponent } from '../../../shared/references/reference-item.component';
@Component({ @Component({
standalone: true,
selector: 'sqx-content-references', selector: 'sqx-content-references',
styleUrls: ['./content-references.component.scss'], styleUrls: ['./content-references.component.scss'],
templateUrl: './content-references.component.html', templateUrl: './content-references.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [ providers: [
Router2State, ComponentContentsState, ComponentContentsState,
Router2State,
],
imports: [
AsyncPipe,
ContentsColumnsPipe,
ListViewComponent,
NgFor,
NgIf,
PagerComponent,
ReferenceItemComponent,
TranslatePipe,
], ],
}) })
export class ContentReferencesComponent implements OnInit, OnDestroy { export class ContentReferencesComponent implements OnInit, OnDestroy {

12
frontend/src/app/features/content/pages/contents/contents-filters-page.component.ts

@ -5,14 +5,24 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ContentsState, defined, Queries, Query, SchemasState, UIState } from '@app/shared'; import { ContentsState, defined, LayoutComponent, Queries, Query, QueryListComponent, SavedQueriesComponent, SchemasState, TranslatePipe, UIState } from '@app/shared';
@Component({ @Component({
standalone: true,
selector: 'sqx-contents-filters-page', selector: 'sqx-contents-filters-page',
styleUrls: ['./contents-filters-page.component.scss'], styleUrls: ['./contents-filters-page.component.scss'],
templateUrl: './contents-filters-page.component.html', templateUrl: './contents-filters-page.component.html',
imports: [
AsyncPipe,
LayoutComponent,
NgIf,
QueryListComponent,
SavedQueriesComponent,
TranslatePipe,
],
}) })
export class ContentsFiltersPageComponent { export class ContentsFiltersPageComponent {
public schemaQueries = public schemaQueries =

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save