Browse Source

Some progress

pull/1/head
Sebastian 10 years ago
parent
commit
283c39a85a
  1. 6
      src/Squidex.Infrastructure/CQRS/EventStore/EventStoreBus.cs
  2. 12
      src/Squidex.Read/ITrackCreatedByEntity.cs
  3. 1
      src/Squidex.Store.MongoDb/Apps/MongoAppEntity.cs
  4. 2
      src/Squidex.Write/Apps/AppCommandHandler.cs
  5. 3
      src/Squidex/Controllers/Api/Schemas/Models/AddFieldDto.cs
  6. 135
      src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs
  7. 2
      src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs
  8. 2
      src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs
  9. 2
      src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs
  10. 32
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  11. 101
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  12. 2
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  13. 4
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  14. 1
      src/Squidex/app/features/settings/pages/clients/client.component.html
  15. 4
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  16. 28
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  17. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  18. 2
      src/Squidex/app/shared/components/app-form.component.html
  19. 4
      src/Squidex/app/shared/components/app-form.component.ts
  20. 49
      src/Squidex/app/shared/services/schemas.service.ts
  21. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.html

6
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))
{

12
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

1
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;

2
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);

3
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; }
}
}

135
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<JObject>(reader);
var subName = jsonObject[discriminator]?.Value<string>();
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<KnownTypeAttribute>()
.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<JsonSchemaAttribute>()?.Name;
return schenaName ?? type.Name;
}
}
}

2
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

2
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
{
/// <summary>

2
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
{
/// <summary>

32
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -1,9 +1,9 @@
<sqx-title message="{app} | Schema" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | {schema}" parameter1="app" value1="{{appName() | async}}" parameter="schema" value2="{{schemaName | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">
<div>
<h3 class="panel-title">Schema</h3>
<h3 class="panel-title">{{schemaName | async}}</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
@ -12,5 +12,33 @@
</div>
<div class="panel-content">
<div class="table-items-footer">
<form class="form-inline" [formGroup]="addFieldForm" (submit)="addField()">
<div class="form-group">
<select class="form-control" formControlName="type">
<option *ngFor="let type of fieldTypes">{{type}}</option>
</select>
</div>
<div class="form-group">
<div class="errors-box" *ngIf="addFieldForm.get('name').invalid && addFieldForm.get('name').dirty">
<div class="errors">
<span *ngIf="addFieldForm.get('name').hasError('required')">
Name is required.
</span>
<span *ngIf="addFieldForm.get('name').hasError('maxlength')">
Name can not have more than 40 characters.
</span>
<span *ngIf="addFieldForm.get('name').hasError('pattern')">
Name can contain lower case letters (a-z), numbers and dashes (not at the end).
</span>
</div>
</div>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter field name" />
</div>
<button type="submit" class="btn btn-success" [disabled]="addFieldForm.invalid">Add Field</button>
</form>
</div>
</div>
</div>

101
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<FieldDto>();
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<string> {
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<FieldDto>) {
this.schemaFields = fields;
this.messageBus.publish(new HistoryChannelUpdated());
}
}

2
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -22,7 +22,7 @@
</div>
</div>
<input type="text" class="form-control" id="schema-name" formControlName="name" />
<input type="text" class="form-control" formControlName="name" />
<span class="form-hint">
<p>

4
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();

1
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -22,6 +22,7 @@
</span>
</div>
</div>
<input type="text" class="form-control" formControlName="name" maxlength="20" sqxFocusOnInit (keydown)="onKeyDown($event.keyCode)" />
</div>

4
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);

28
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -21,22 +21,22 @@
<div class="table-items-footer">
<form class="form-inline" [formGroup]="addClientForm" (submit)="attachClient()">
<div class="errors-box" *ngIf="addClientForm.get('name').invalid && addClientForm.get('name').dirty">
<div class="errors">
<span *ngIf="addClientForm.get('name').hasError('required')">
Name is required.
</span>
<span *ngIf="addClientForm.get('name').hasError('maxlength')">
Name can not have more than 40 characters.
</span>
<span *ngIf="addClientForm.get('name').hasError('pattern')">
Name can contain lower case letters (a-z), numbers and dashes (not at the end).
</span>
<div class="form-group">
<div class="errors-box" *ngIf="addClientForm.get('name').invalid && addClientForm.get('name').dirty">
<div class="errors">
<span *ngIf="addClientForm.get('name').hasError('required')">
Name is required.
</span>
<span *ngIf="addClientForm.get('name').hasError('maxlength')">
Name can not have more than 40 characters.
</span>
<span *ngIf="addClientForm.get('name').hasError('pattern')">
Name can contain lower case letters (a-z), numbers and dashes (not at the end).
</span>
</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" id="app-name" formControlName="name" maxlength="40" placeholder="Enter client name" />
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter client name" />
</div>
<button type="submit" class="btn btn-success" [disabled]="addClientForm.invalid">Add Client</button>

2
src/Squidex/app/features/settings/pages/clients/clients-page.component.ts

@ -94,7 +94,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
if (this.addClientForm.valid) {
this.addClientForm.disable();
const dto = new CreateAppClientDto(this.addClientForm.controls['name'].value);
const dto = new CreateAppClientDto(this.addClientForm.get('name').value);
const reset = () => {
this.addClientForm.reset();

2
src/Squidex/app/shared/components/app-form.component.html

@ -22,7 +22,7 @@
</div>
</div>
<input type="text" class="form-control" id="app-name" formControlName="name" />
<input type="text" class="form-control" formControlName="name" />
<span class="form-hint">
<p>

4
src/Squidex/app/shared/components/app-form.component.ts

@ -53,7 +53,7 @@ export class AppFormComponent implements OnInit {
}
public ngOnInit() {
this.createForm.controls['name'].valueChanges.subscribe(value => {
this.createForm.get('name').valueChanges.subscribe(value => {
this.appName = value || FALLBACK_NAME;
});
}
@ -64,7 +64,7 @@ export class AppFormComponent implements OnInit {
if (this.createForm.valid) {
this.createForm.disable();
const dto = new CreateAppDto(this.createForm.controls['name'].value);
const dto = new CreateAppDto(this.createForm.get('name').value);
this.appsStore.createApp(dto)
.subscribe(dto => {

49
src/Squidex/app/shared/services/schemas.service.ts

@ -63,30 +63,30 @@ export abstract class FieldPropertiesDto {
}
export class NumberFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly defaultValue: number | null,
public readonly maxValue: number | null,
public readonly minValue: number | null,
public readonly allowedValues: number[]
constructor(label?: string, hints?: string, placeholder?: string, isRequired: boolean = false,
public readonly defaultValue?: number | null,
public readonly maxValue?: number | null,
public readonly minValue?: number | null,
public readonly allowedValues?: number[]
) {
super(label, hints, placeholder, isRequired);
this['$type'] = 'Number';
this['fieldType'] = 'number';
}
}
export class StringFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly defaultValue: string,
public readonly pattern: string,
public readonly patternMessage: string,
public readonly minLength: number | null,
public readonly maxLength: number | null,
public readonly allowedValues: number[]
constructor(label?: string, hints?: string, placeholder?: string, isRequired: boolean = false,
public readonly defaultValue?: string,
public readonly pattern?: string,
public readonly patternMessage?: string,
public readonly minLength?: number | null,
public readonly maxLength?: number | null,
public readonly allowedValues?: string[]
) {
super(label, hints, placeholder, isRequired);
this['$type'] = 'String';
this['fieldType'] = 'string';
}
}
@ -98,6 +98,14 @@ export class UpdateSchemaDto {
}
}
export class AddFieldDto {
constructor(
public readonly name: string,
public readonly properties: FieldPropertiesDto
) {
}
}
export class UpdateFieldDto {
constructor(
public readonly properties: FieldPropertiesDto
@ -142,7 +150,7 @@ export class SchemasService {
}
public getSchema(appName: string, id: string): Observable<SchemaDetailsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/{id}`);
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${id}`);
return this.authService.authGet(url)
.map(response => response.json())
@ -206,4 +214,15 @@ export class SchemasService {
})
.catch(response => handleError('Failed to create schema. Please reload.', response));
}
public postField(appName: string, schemaName: string, dto: AddFieldDto): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/`);
return this.authService.authPost(url, dto)
.map(response => response.json())
.map(response => {
return new EntityCreatedDto(response.id);
})
.catch(response => handleError('Failed to add field. Please reload.', response));
}
}

2
src/Squidex/app/shell/pages/internal/apps-menu.component.html

@ -17,7 +17,7 @@
</div>
<div class="drodown-button">
<button class="btn btn-block btn-success" id="app-create" (click)="createApp()"><i class="icon-plus"></i> Create New App</button>
<button class="btn btn-block btn-success" (click)="createApp()"><i class="icon-plus"></i> Create New App</button>
</div>
</div>
</li>

Loading…
Cancel
Save