From 283c39a85ac8df28ac59d5054aa0e4090785574e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 18 Dec 2016 17:41:13 +0100 Subject: [PATCH] Some progress --- .../CQRS/EventStore/EventStoreBus.cs | 6 +- src/Squidex.Read/ITrackCreatedByEntity.cs | 12 +- .../Apps/MongoAppEntity.cs | 1 - src/Squidex.Write/Apps/AppCommandHandler.cs | 2 +- .../Api/Schemas/Models/AddFieldDto.cs | 3 + .../Converters/JsonInheritanceConverter.cs | 135 ++++++++++++++++++ .../Api/Schemas/Models/FieldPropertiesDto.cs | 2 +- .../Models/NumberFieldPropertiesDto.cs | 2 +- .../Models/StringFieldPropertiesDto.cs | 2 +- .../pages/schema/schema-page.component.html | 32 ++++- .../pages/schema/schema-page.component.ts | 101 ++++++++++++- .../pages/schemas/schema-form.component.html | 2 +- .../pages/schemas/schema-form.component.ts | 4 +- .../pages/clients/client.component.html | 1 + .../pages/clients/client.component.ts | 4 +- .../pages/clients/clients-page.component.html | 28 ++-- .../pages/clients/clients-page.component.ts | 2 +- .../shared/components/app-form.component.html | 2 +- .../shared/components/app-form.component.ts | 4 +- .../app/shared/services/schemas.service.ts | 49 +++++-- .../pages/internal/apps-menu.component.html | 2 +- 21 files changed, 339 insertions(+), 57 deletions(-) create mode 100644 src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs diff --git a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs index d2c7a6af9..a1693d4c7 100644 --- a/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs +++ b/src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs @@ -81,7 +81,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore return; } - this.streamName = streamToConnect; + streamName = streamToConnect; SubscribeLive(); SubscribeCatch(); @@ -145,7 +145,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore connection.SubscribeToStreamFrom(streamName, position, settings, (subscription, resolvedEvent) => { - OnCatchEvent(consumer, streamName, resolvedEvent, subscriptionName, subscription); + OnCatchEvent(consumer, resolvedEvent, subscriptionName, subscription); }, userCredentials: credentials); lock (catchSubscriptions) @@ -175,7 +175,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore } } - private void OnCatchEvent(IEventConsumer consumer, string streamName, ResolvedEvent resolvedEvent, string subscriptionName, EventStoreCatchUpSubscription subscription) + private void OnCatchEvent(IEventConsumer consumer, ResolvedEvent resolvedEvent, string subscriptionName, EventStoreCatchUpSubscription subscription) { if (resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$", StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Squidex.Read/ITrackCreatedByEntity.cs b/src/Squidex.Read/ITrackCreatedByEntity.cs index 4525c1a63..6d79a3f7f 100644 --- a/src/Squidex.Read/ITrackCreatedByEntity.cs +++ b/src/Squidex.Read/ITrackCreatedByEntity.cs @@ -1,7 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +// ========================================================================== +// ITrackCreatedByEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + using Squidex.Infrastructure; namespace Squidex.Read diff --git a/src/Squidex.Store.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Store.MongoDb/Apps/MongoAppEntity.cs index bcb619bef..d3c501e8b 100644 --- a/src/Squidex.Store.MongoDb/Apps/MongoAppEntity.cs +++ b/src/Squidex.Store.MongoDb/Apps/MongoAppEntity.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; using System.Linq; using MongoDB.Bson.Serialization.Attributes; diff --git a/src/Squidex.Write/Apps/AppCommandHandler.cs b/src/Squidex.Write/Apps/AppCommandHandler.cs index baacc67e5..6980b0c47 100644 --- a/src/Squidex.Write/Apps/AppCommandHandler.cs +++ b/src/Squidex.Write/Apps/AppCommandHandler.cs @@ -64,7 +64,7 @@ namespace Squidex.Write.Apps if (await userRepository.FindUserByIdAsync(command.ContributorId) == null) { var error = - new ValidationError($"Cannot find contributor the contributor", + new ValidationError("Cannot find contributor the contributor", nameof(AssignContributor.ContributorId)); throw new ValidationException("Cannot assign contributor to app", error); diff --git a/src/Squidex/Controllers/Api/Schemas/Models/AddFieldDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/AddFieldDto.cs index e22a82ff4..878968f2b 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/AddFieldDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/AddFieldDto.cs @@ -8,6 +8,8 @@ using System.ComponentModel.DataAnnotations; +// ReSharper disable ConvertIfStatementToReturnStatement + namespace Squidex.Controllers.Api.Schemas.Models { public sealed class AddFieldDto @@ -26,3 +28,4 @@ namespace Squidex.Controllers.Api.Schemas.Models public FieldPropertiesDto Properties { get; set; } } } + diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs b/src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs new file mode 100644 index 000000000..f9129d450 --- /dev/null +++ b/src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs @@ -0,0 +1,135 @@ +// ========================================================================== +// JsonInheritanceConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NJsonSchema.Annotations; +// ReSharper disable ConvertIfStatementToReturnStatement + +namespace Squidex.Controllers.Api.Schemas.Models.Converters +{ + public sealed class JsonInheritanceConverter : JsonConverter + { + private readonly string discriminator; + + [ThreadStatic] + private static bool isReading; + + [ThreadStatic] + private static bool isWriting; + + public override bool CanWrite + { + get + { + if (!isWriting) + { + return true; + } + + return isWriting = false; + } + } + + public override bool CanRead + { + get + { + if (!isReading) + { + return true; + } + + return isReading = false; + } + } + + public JsonInheritanceConverter(string discriminator) + { + this.discriminator = discriminator; + } + + public override bool CanConvert(Type objectType) + { + return true; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + isWriting = true; + try + { + var jsonObject = JObject.FromObject(value, serializer); + + jsonObject.AddFirst(new JProperty(discriminator, GetSchemaName(value.GetType()))); + + writer.WriteToken(jsonObject.CreateReader()); + } + finally + { + isWriting = false; + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + isReading = true; + try + { + var jsonObject = serializer.Deserialize(reader); + + var subName = jsonObject[discriminator]?.Value(); + + if (subName == null) + { + return null; + } + + var subType = GetObjectSubtype(objectType, subName); + + if (subType == null) + { + return null; + } + + return serializer.Deserialize(jsonObject.CreateReader(), subType); + } + finally + { + isReading = false; + } + } + + private static Type GetObjectSubtype(Type objectType, string discriminatorValue) + { + var knownTypeAttribute = + objectType.GetTypeInfo().GetCustomAttributes() + .FirstOrDefault(a => IsKnownType(a, discriminatorValue)); + + return knownTypeAttribute?.Type; + } + + private static bool IsKnownType(KnownTypeAttribute attribute, string discriminator) + { + var type = attribute.Type; + + return type != null && GetSchemaName(type) == discriminator; + } + + private static string GetSchemaName(Type type) + { + var schenaName = type.GetTypeInfo().GetCustomAttribute()?.Name; + + return schenaName ?? type.Name; + } + } +} \ No newline at end of file diff --git a/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs index bcd32ba76..8f4b96f10 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs @@ -9,7 +9,7 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Newtonsoft.Json; -using NJsonSchema.Converters; +using Squidex.Controllers.Api.Schemas.Models.Converters; using Squidex.Core.Schemas; namespace Squidex.Controllers.Api.Schemas.Models diff --git a/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs index 59d9ec6e1..6cd058220 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure.Reflection; namespace Squidex.Controllers.Api.Schemas.Models { - [JsonSchema("Number")] + [JsonSchema("number")] public sealed class NumberFieldPropertiesDto : FieldPropertiesDto { /// diff --git a/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs index 0ecd66834..7829f5dd0 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure.Reflection; namespace Squidex.Controllers.Api.Schemas.Models { - [JsonSchema("String")] + [JsonSchema("string")] public sealed class StringFieldPropertiesDto : FieldPropertiesDto { /// diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html index 45e70633f..38df416a0 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -1,9 +1,9 @@ - +
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index eb6bd8a51..3d88fac76 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -6,27 +6,120 @@ */ import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; import { + AddFieldDto, AppComponentBase, AppsStoreService, + FieldDto, + FieldPropertiesDto, + HistoryChannelUpdated, + ImmutableArray, + MessageBus, NotificationService, + NumberFieldPropertiesDto, + SchemaDetailsDto, SchemasService, + StringFieldPropertiesDto, UsersProviderService } from 'shared'; -const FALLBACK_NAME = 'my-schema'; - @Component({ selector: 'sqx-schema-page', styleUrls: ['./schema-page.component.scss'], templateUrl: './schema-page.component.html' }) -export class SchemaPageComponent extends AppComponentBase { +export class SchemaPageComponent extends AppComponentBase implements OnInit { + public fieldTypes: string[] = [ + 'string', + 'number' + ]; + + public schemaFields = ImmutableArray.empty(); + + public addFieldForm: FormGroup = + this.formBuilder.group({ + type: ['string', + [ + Validators.required + ]], + name: ['', + [ + Validators.required, + Validators.maxLength(40), + Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*') + ]] + }); + + public get schemaName(): Observable { + return this.route.params.map(p => p['schemaName']); + } + constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, - private readonly schemasService: SchemasService + private readonly schemasService: SchemasService, + private readonly messageBus: MessageBus, + private readonly formBuilder: FormBuilder, + private readonly route: ActivatedRoute ) { super(apps, notifications, users); } + + public ngOnInit() { + this.load(); + } + + public load() { + this.schemaName.combineLatest(this.appName(), (schemaName, appName) => { return { schemaName, appName }; }) + .switchMap(p => this.schemasService.getSchema(p.appName, p.schemaName)).retry(2) + .subscribe(dto => { + this.schemaFields = ImmutableArray.of(dto.fields); + }, error => { + this.notifyError(error); + }); + } + + public addField() { + this.addFieldForm.markAsTouched(); + + if (this.addFieldForm.valid) { + this.addFieldForm.disable(); + + let properties: FieldPropertiesDto; + + switch (this.addFieldForm.get('type').value) { + case 'string': + properties = new StringFieldPropertiesDto(); + break; + case 'number': + properties = new NumberFieldPropertiesDto(); + } + + const dto = new AddFieldDto(this.addFieldForm.get('name').value, properties); + + const reset = () => { + this.addFieldForm.reset(); + this.addFieldForm.enable(); + }; + + this.schemaName.combineLatest(this.appName(), (schemaName, appName) => { return { schemaName, appName }; }) + .switchMap(p => this.schemasService.postField(p.appName, p.schemaName, dto)) + .subscribe(dto => { + this.updateFields(this.schemaFields.push(new FieldDto(this.addFieldForm.get('name').value, false, false, properties))); + reset(); + }, error => { + this.notifyError(error); + reset(); + }); + } + } + + private updateFields(fields: ImmutableArray) { + this.schemaFields = fields; + + this.messageBus.publish(new HistoryChannelUpdated()); + } } diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html index ef42272fb..d9ad7b88c 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html @@ -22,7 +22,7 @@
- +

diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts index e93f4b6c2..957e0ece2 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts @@ -61,7 +61,7 @@ export class SchemaFormComponent implements OnInit { } public ngOnInit() { - this.createForm.controls['name'].valueChanges.subscribe(value => { + this.createForm.get('name').valueChanges.subscribe(value => { this.schemaName = value || FALLBACK_NAME; }); } @@ -72,7 +72,7 @@ export class SchemaFormComponent implements OnInit { if (this.createForm.valid) { this.createForm.disable(); - const name = this.createForm.controls['name'].value; + const name = this.createForm.get('name').value; const dto = new CreateSchemaDto(name); const now = DateTime.now(); diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.html b/src/Squidex/app/features/settings/pages/clients/client.component.html index 257f4387a..9adc18a89 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.html +++ b/src/Squidex/app/features/settings/pages/clients/client.component.html @@ -22,6 +22,7 @@ + diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.ts b/src/Squidex/app/features/settings/pages/clients/client.component.ts index eae009b45..5c2bbd385 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.ts +++ b/src/Squidex/app/features/settings/pages/clients/client.component.ts @@ -74,7 +74,7 @@ export class ClientComponent { } public resetForm() { - this.renameForm.controls['name'].setValue(this.clientName); + this.renameForm.get('name').setValue(this.clientName); } public cancelRename() { @@ -95,7 +95,7 @@ export class ClientComponent { public rename() { try { - const newName = this.renameForm.controls['name'].value; + const newName = this.renameForm.get('name').value; if (newName !== this.clientName) { this.renamed.emit(newName); diff --git a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html index 69db997b8..f7b0bc77c 100644 --- a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html +++ b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html @@ -21,22 +21,22 @@

- +