diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..95317685c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Qaisar Ahmad & Sebastian Stehle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index a7e5bc73d..3fced5012 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# Squidex +![Squidex Logo](media/logo-wide.png "Squidex") + +# What is Squidex? + +Squidex is an open source headless CMS and content management hub. In contrast to an traditional CMS Squidex provides a rich API with OData filter support and Swagger Definitions. It is up to you to build you UI on top of it. It can be website, a native App or just another server. +We built it on top of ASP.NET Core and CQRS and is tested for Windows and Linux on modern Browsers. + +[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/squidex-cms/Lobby) + +Read the docs on [https://docs.squidex.io/](https://docs.squidex.io/) (work in progress) or just check out the code and play around. + +## Prerequisites + +* [Visual Studio Code](https://code.visualstudio.com/) or [Visual Studio 2017](https://www.visualstudio.com/vs/visual-studio-2017-rc/) +* [Node.js](https://nodejs.org/en/) +* [.NET Core SDK](https://www.microsoft.com/net/download/core#/current) (Already part of Visual Studio 2017) +* [MongoDB](https://www.mongodb.com/) +* [Redis](https://redis.io/download) (If you want to run Squidex on multiple hosts) + +## Contributors + +* [Qaisar Ahmad](http://www.qaisarahmad.com/) Interaction Designer, Pakistan +* [Sebastian Stehle](https://github.com/SebastianStehle) Software Engineer, German (currently Sweden) + +## Contributing + +Please create issues to report bugs, suggest new functionalities, ask questions or just share your thoughts about the project. We will really appreciate your contribution, thanks. + +## Cloud Version + +Although Squidex is free we are also working on a Saas version on [Https://cloud.squidex.io](https://cloud.squidex.io) (More information coming soon). We have also have plans to sell a premium version with first class support and some exlusive features. But don't be afraid, our first priority is to deliver a state of the art, stable, fast and free content management hub to make the life for all developers a little bit easier. diff --git a/Squidex.sln.DotSettings b/Squidex.sln.DotSettings index 8404cbba9..0adccb11b 100644 --- a/Squidex.sln.DotSettings +++ b/Squidex.sln.DotSettings @@ -27,6 +27,7 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW + DO_NOT_SHOW DO_NOT_SHOW diff --git a/src/Squidex.Core/Identity/SquidexClaimTypes.cs b/src/Squidex.Core/Identity/SquidexClaimTypes.cs index cd6c79d0f..bbdb0877a 100644 --- a/src/Squidex.Core/Identity/SquidexClaimTypes.cs +++ b/src/Squidex.Core/Identity/SquidexClaimTypes.cs @@ -10,10 +10,10 @@ namespace Squidex.Core.Identity { public class SquidexClaimTypes { - public const string SquidexDisplayName = "urn:squidex:name"; + public static readonly string SquidexDisplayName = "urn:squidex:name"; - public const string SquidexPictureUrl = "urn:squidex:picture"; + public static readonly string SquidexPictureUrl = "urn:squidex:picture"; - public const string Prefix = "urn:squidex:"; + public static readonly string Prefix = "urn:squidex:"; } } diff --git a/src/Squidex.Core/Identity/SquidexRoles.cs b/src/Squidex.Core/Identity/SquidexRoles.cs index d7e530c9c..ca0e9b3cb 100644 --- a/src/Squidex.Core/Identity/SquidexRoles.cs +++ b/src/Squidex.Core/Identity/SquidexRoles.cs @@ -10,12 +10,12 @@ namespace Squidex.Core.Identity { public static class SquidexRoles { - public const string Administrator = "ADMINISTRATOR"; + public static readonly string Administrator = "ADMINISTRATOR"; - public const string AppOwner = "APP-OWNER"; + public static readonly string AppOwner = "APP-OWNER"; - public const string AppEditor = "APP-EDITOR"; + public static readonly string AppEditor = "APP-EDITOR"; - public const string AppDeveloper = "APP-DEVELOPER"; + public static readonly string AppDeveloper = "APP-DEVELOPER"; } } diff --git a/src/Squidex.Core/Schemas/Json/JsonFieldModel.cs b/src/Squidex.Core/Schemas/Json/JsonFieldModel.cs index 0394327f6..8697a94ea 100644 --- a/src/Squidex.Core/Schemas/Json/JsonFieldModel.cs +++ b/src/Squidex.Core/Schemas/Json/JsonFieldModel.cs @@ -10,12 +10,12 @@ namespace Squidex.Core.Schemas.Json { public sealed class JsonFieldModel { - public string Name; + public string Name { get; set; } - public bool IsHidden; + public bool IsHidden { get; set; } - public bool IsDisabled; + public bool IsDisabled { get; set; } - public FieldProperties Properties; + public FieldProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/Json/JsonSchemaModel.cs b/src/Squidex.Core/Schemas/Json/JsonSchemaModel.cs index b129a60da..3df33cfad 100644 --- a/src/Squidex.Core/Schemas/Json/JsonSchemaModel.cs +++ b/src/Squidex.Core/Schemas/Json/JsonSchemaModel.cs @@ -12,12 +12,12 @@ namespace Squidex.Core.Schemas.Json { public sealed class JsonSchemaModel { - public string Name; + public string Name { get; set; } - public bool IsPublished; + public bool IsPublished { get; set; } - public SchemaProperties Properties; + public SchemaProperties Properties { get; set; } - public Dictionary Fields; + public Dictionary Fields { get; set; } } } \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs b/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs index 87146f14b..31dd33e94 100644 --- a/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs +++ b/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs @@ -74,7 +74,7 @@ namespace Squidex.Core.Schemas.Json } return field; - }).ToDictionary(x => x.Id, x => x).ToImmutableDictionary(); + }).ToImmutableDictionary(x => x.Id, x => x); var schema = new Schema( diff --git a/src/Squidex.Core/Schemas/Schema.cs b/src/Squidex.Core/Schemas/Schema.cs index 83994dea4..dba1f78b9 100644 --- a/src/Squidex.Core/Schemas/Schema.cs +++ b/src/Squidex.Core/Schemas/Schema.cs @@ -266,13 +266,7 @@ namespace Squidex.Core.Schemas Guard.NotNull(errors, nameof(errors)); Guard.NotEmpty(languages, nameof(languages)); - foreach (var fieldData in data) - { - if (!fieldsByName.ContainsKey(fieldData.Key)) - { - errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key)); - } - } + ValidateUnknownFields(data, errors); foreach (var field in fieldsByName.Values) { @@ -281,35 +275,11 @@ namespace Squidex.Core.Schemas if (field.RawProperties.IsLocalizable) { - foreach (var valueLanguage in fieldData.Keys) - { - if (!Language.TryGetLanguage(valueLanguage, out Language language)) - { - fieldErrors.Add($"{field.Name} has an invalid language '{valueLanguage}'"); - } - else if (!languages.Contains(language)) - { - fieldErrors.Add($"{field.Name} has an unsupported language '{valueLanguage}'"); - } - } - - foreach (var language in languages) - { - var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull()); - - await field.ValidateAsync(value, fieldErrors, language); - } + await ValidateLocalizableFieldAsync(languages, fieldData, fieldErrors, field); } else { - if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code)) - { - fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})"); - } - - var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull()); - - await field.ValidateAsync(value, fieldErrors); + await ValidateNonLocalizableField(fieldData, fieldErrors, field); } foreach (var error in fieldErrors) @@ -319,6 +289,51 @@ namespace Squidex.Core.Schemas } } + private void ValidateUnknownFields(ContentData data, IList errors) + { + foreach (var fieldData in data) + { + if (!fieldsByName.ContainsKey(fieldData.Key)) + { + errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key)); + } + } + } + + private static async Task ValidateLocalizableFieldAsync(HashSet languages, ContentFieldData fieldData, List fieldErrors, Field field) + { + foreach (var valueLanguage in fieldData.Keys) + { + if (!Language.TryGetLanguage(valueLanguage, out Language language)) + { + fieldErrors.Add($"{field.Name} has an invalid language '{valueLanguage}'"); + } + else if (!languages.Contains(language)) + { + fieldErrors.Add($"{field.Name} has an unsupported language '{valueLanguage}'"); + } + } + + foreach (var language in languages) + { + var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull()); + + await field.ValidateAsync(value, fieldErrors, language); + } + } + + private static async Task ValidateNonLocalizableField(ContentFieldData fieldData, List fieldErrors, Field field) + { + if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code)) + { + fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})"); + } + + var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull()); + + await field.ValidateAsync(value, fieldErrors); + } + public void Enrich(ContentData data, HashSet languages) { Guard.NotNull(data, nameof(data)); diff --git a/src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs b/src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs similarity index 89% rename from src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs rename to src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs index ee0ccdd6d..ca7ad3a3b 100644 --- a/src/Squidex.Infrastructure.Redis/InfrastructureErrors.cs +++ b/src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs @@ -1,5 +1,5 @@ // ========================================================================== -// InfrastructureErrors.cs +// RedisInfrastructureErrors.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace Squidex.Infrastructure.Redis { - public class InfrastructureErrors + public class RedisInfrastructureErrors { public static readonly EventId InvalidatingReceivedFailed = new EventId(50001, "InvalidingReceivedFailed"); diff --git a/src/Squidex.Infrastructure.Redis/RedisSubscription.cs b/src/Squidex.Infrastructure.Redis/RedisSubscription.cs index b5db338b1..045858ce4 100644 --- a/src/Squidex.Infrastructure.Redis/RedisSubscription.cs +++ b/src/Squidex.Infrastructure.Redis/RedisSubscription.cs @@ -44,7 +44,7 @@ namespace Squidex.Infrastructure.Redis } catch (Exception ex) { - logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, ex, "Failed to send invalidation message {0}", token); + logger.LogError(RedisInfrastructureErrors.InvalidatingReceivedFailed, ex, "Failed to send invalidation message {0}", token); } } @@ -78,7 +78,7 @@ namespace Squidex.Infrastructure.Redis } catch (Exception ex) { - logger.LogError(InfrastructureErrors.InvalidatingReceivedFailed, ex, "Failed to receive invalidation message."); + logger.LogError(RedisInfrastructureErrors.InvalidatingReceivedFailed, ex, "Failed to receive invalidation message."); } } diff --git a/src/Squidex.Infrastructure/CQRS/CommonHeaders.cs b/src/Squidex.Infrastructure/CQRS/CommonHeaders.cs index eecc2275d..9274e62e2 100644 --- a/src/Squidex.Infrastructure/CQRS/CommonHeaders.cs +++ b/src/Squidex.Infrastructure/CQRS/CommonHeaders.cs @@ -10,16 +10,16 @@ namespace Squidex.Infrastructure.CQRS { public sealed class CommonHeaders { - public const string AggregateId = "AggregateId"; + public static readonly string AggregateId = "AggregateId"; - public const string CommitId = "CommitId"; + public static readonly string CommitId = "CommitId"; - public const string EventId = "EventId"; + public static readonly string EventId = "EventId"; - public const string EventNumber = "EventNumber"; + public static readonly string EventNumber = "EventNumber"; - public const string Timestamp = "Timestamp"; + public static readonly string Timestamp = "Timestamp"; - public const string Actor = "Actor"; + public static readonly string Actor = "Actor"; } } diff --git a/src/Squidex.Infrastructure/Security/OpenIdClaims.cs b/src/Squidex.Infrastructure/Security/OpenIdClaims.cs index 145fa02fd..1fe650780 100644 --- a/src/Squidex.Infrastructure/Security/OpenIdClaims.cs +++ b/src/Squidex.Infrastructure/Security/OpenIdClaims.cs @@ -13,31 +13,31 @@ namespace Squidex.Infrastructure.Security /// /// Unique Identifier for the End-User at the Issuer. /// - public const string Subject = "sub"; + public static readonly string Subject = "sub"; /// /// The client id claim. /// - public const string ClientId = "client_id"; + public static readonly string ClientId = "client_id"; /// /// End-User's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the End-User's locale and preferences. /// - public const string Name = "name"; + public static readonly string Name = "name"; /// /// Casual name of the End-User that may or may not be the same as the given_name. For instance, a nickname value of Mike might be returned alongside a given_name value of Michael. /// - public const string NickName = "nickname"; + public static readonly string NickName = "nickname"; /// /// Shorthand name by which the End-User wishes to be referred to at the /// - public const string PreferredUserName = "preferred_username"; + public static readonly string PreferredUserName = "preferred_username"; /// /// End-User's preferred e-mail address. /// - public const string Email = "email"; + public static readonly string Email = "email"; } } diff --git a/src/Squidex.Read.MongoDb/Utils/CollectionExtensions.cs b/src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs similarity index 97% rename from src/Squidex.Read.MongoDb/Utils/CollectionExtensions.cs rename to src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs index a3ce7e418..a3fee940e 100644 --- a/src/Squidex.Read.MongoDb/Utils/CollectionExtensions.cs +++ b/src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs @@ -16,7 +16,7 @@ using Squidex.Infrastructure.MongoDb; namespace Squidex.Read.MongoDb.Utils { - public static class CollectionExtensions + public static class MongoCollectionExtensions { public static Task CreateAsync(this IMongoCollection collection, SquidexEvent @event, EnvelopeHeaders headers, Action updater) where T : MongoEntity, new() { diff --git a/src/Squidex/Config/Constants.cs b/src/Squidex/Config/Constants.cs index 58f765584..0c994fb2a 100644 --- a/src/Squidex/Config/Constants.cs +++ b/src/Squidex/Config/Constants.cs @@ -10,16 +10,16 @@ namespace Squidex.Config { public class Constants { - public const string ApiPrefix = "/api"; + public static readonly string ApiPrefix = "/api"; - public const string ApiScope = "squidex-api"; + public static readonly string ApiScope = "squidex-api"; - public const string RoleScope = "role"; + public static readonly string RoleScope = "role"; - public const string ProfileScope = "squidex-profile"; + public static readonly string ProfileScope = "squidex-profile"; - public const string FrontendClient = "squidex-frontend"; + public static readonly string FrontendClient = "squidex-frontend"; - public const string IdentityPrefix = "/identity-server"; + public static readonly string IdentityPrefix = "/identity-server"; } } diff --git a/src/Squidex/Pipeline/Swagger/SwaggerHelper.cs b/src/Squidex/Pipeline/Swagger/SwaggerHelper.cs index 6be947320..7494341d8 100644 --- a/src/Squidex/Pipeline/Swagger/SwaggerHelper.cs +++ b/src/Squidex/Pipeline/Swagger/SwaggerHelper.cs @@ -18,7 +18,7 @@ namespace Squidex.Pipeline.Swagger { public static class SwaggerHelper { - private static ConcurrentDictionary docs = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary docs = new ConcurrentDictionary(); public static string LoadDocs(string name) {