Browse Source

Create api returns new content, populated with default values.

pull/1/head
Sebastian 10 years ago
parent
commit
857af0f0f8
  1. 7
      src/Squidex.Core/Schemas/Field.cs
  2. 8
      src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs
  3. 27
      src/Squidex.Write/Apps/AppCommandHandler.cs
  4. 24
      src/Squidex.Write/Contents/ContentCommandHandler.cs
  5. 9
      src/Squidex.Write/Schemas/SchemaCommandHandler.cs
  6. 9
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  7. 31
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  8. 23
      src/Squidex/Controllers/ContentApi/Models/ContentDto.cs
  9. 2
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  10. 3
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  11. 17
      src/Squidex/app/framework/angular/http-utils.ts
  12. 22
      src/Squidex/app/shared/services/contents.service.spec.ts
  13. 13
      src/Squidex/app/shared/services/contents.service.ts
  14. 5
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs
  15. 3
      tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs
  16. 3
      tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs

7
src/Squidex.Core/Schemas/Field.cs

@ -74,9 +74,12 @@ namespace Squidex.Core.Schemas
var defaultValue = RawProperties.GetDefaultValue();
if (!RawProperties.IsRequired && defaultValue != null && fieldData.GetOrDefault(language.Iso2Code) == null)
if (!RawProperties.IsRequired && defaultValue != null)
{
fieldData.AddValue(language.Iso2Code, defaultValue);
if (!fieldData.TryGetValue(language.Iso2Code, out JToken value) || value == null || value.Type == JTokenType.Null)
{
fieldData.AddValue(language.Iso2Code, defaultValue);
}
}
}

8
src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs

@ -18,4 +18,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
IdOrValue = idOrValue;
}
}
public static class EntityCreatedResult
{
public static EntityCreatedResult<T> Create<T>(T idOrValue, long version)
{
return new EntityCreatedResult<T>(idOrValue, version);
}
}
}

27
src/Squidex.Write/Apps/AppCommandHandler.cs

@ -52,7 +52,12 @@ namespace Squidex.Write.Apps
throw new ValidationException("Cannot create a new app", error);
}
await handler.CreateAsync<AppDomainObject>(context, x => x.Create(command));
await handler.CreateAsync<AppDomainObject>(context, a =>
{
a.Create(command);
context.Succeed(EntityCreatedResult.Create(a.Id, a.Version));
});
}
protected async Task On(AssignContributor command, CommandContext context)
@ -66,47 +71,47 @@ namespace Squidex.Write.Apps
throw new ValidationException("Cannot assign contributor to app", error);
}
await handler.UpdateAsync<AppDomainObject>(context, x => x.AssignContributor(command));
await handler.UpdateAsync<AppDomainObject>(context, a => a.AssignContributor(command));
}
protected Task On(AttachClient command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x =>
return handler.UpdateAsync<AppDomainObject>(context, a =>
{
x.AttachClient(command, keyGenerator.GenerateKey());
a.AttachClient(command, keyGenerator.GenerateKey());
context.Succeed(new EntityCreatedResult<AppClient>(x.Clients[command.Id], x.Version));
context.Succeed(EntityCreatedResult.Create(a.Clients[command.Id], a.Version));
});
}
protected Task On(RemoveContributor command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x => x.RemoveContributor(command));
return handler.UpdateAsync<AppDomainObject>(context, a => a.RemoveContributor(command));
}
protected Task On(RenameClient command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x => x.RenameClient(command));
return handler.UpdateAsync<AppDomainObject>(context, a => a.RenameClient(command));
}
protected Task On(RevokeClient command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x => x.RevokeClient(command));
return handler.UpdateAsync<AppDomainObject>(context, a => a.RevokeClient(command));
}
protected Task On(AddLanguage command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x => x.AddLanguage(command));
return handler.UpdateAsync<AppDomainObject>(context, a => a.AddLanguage(command));
}
protected Task On(RemoveLanguage command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x => x.RemoveLanguage(command));
return handler.UpdateAsync<AppDomainObject>(context, a => a.RemoveLanguage(command));
}
protected Task On(SetMasterLanguage command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, x => x.SetMasterLanguage(command));
return handler.UpdateAsync<AppDomainObject>(context, a => a.SetMasterLanguage(command));
}
public Task<bool> HandleAsync(CommandContext context)

24
src/Squidex.Write/Contents/ContentCommandHandler.cs

@ -42,9 +42,14 @@ namespace Squidex.Write.Contents
protected async Task On(CreateContent command, CommandContext context)
{
await ValidateAsync(command, () => "Failed to create content");
await ValidateAsync(command, () => "Failed to create content", true);
await handler.CreateAsync<ContentDomainObject>(context, c => c.Create(command));
await handler.CreateAsync<ContentDomainObject>(context, c =>
{
c.Create(command);
context.Succeed(EntityCreatedResult.Create(command.Data, c.Version));
});
}
protected async Task On(UpdateContent command, CommandContext context)
@ -81,15 +86,13 @@ namespace Squidex.Write.Contents
return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
}
private async Task ValidateAsync(ContentDataCommand command, Func<string> message)
private async Task ValidateAsync(ContentDataCommand command, Func<string> message, bool enrich = false)
{
Guard.Valid(command, nameof(command), message);
var taskForApp =
appProvider.FindAppByIdAsync(command.AppId.Id);
var taskForApp = appProvider.FindAppByIdAsync(command.AppId.Id);
var taskForSchema =
schemas.FindSchemaByIdAsync(command.SchemaId.Id);
var taskForSchema = schemas.FindSchemaByIdAsync(command.SchemaId.Id);
await Task.WhenAll(taskForApp, taskForSchema);
@ -100,12 +103,15 @@ namespace Squidex.Write.Contents
await schemaObject.ValidateAsync(command.Data, schemaErrors, languages);
schemaObject.Enrich(command.Data, languages);
if (schemaErrors.Count > 0)
{
throw new ValidationException(message(), schemaErrors);
}
if (enrich)
{
schemaObject.Enrich(command.Data, languages);
}
}
}
}

9
src/Squidex.Write/Schemas/SchemaCommandHandler.cs

@ -42,7 +42,12 @@ namespace Squidex.Write.Schemas
throw new ValidationException("Cannot create a new schema", error);
}
await handler.CreateAsync<SchemaDomainObject>(context, s => s.Create(command));
await handler.CreateAsync<SchemaDomainObject>(context, s =>
{
s.Create(command);
context.Succeed(EntityCreatedResult.Create(s.Id, s.Version));
});
}
protected Task On(AddField command, CommandContext context)
@ -51,7 +56,7 @@ namespace Squidex.Write.Schemas
{
s.AddField(command);
context.Succeed(new EntityCreatedResult<long>(s.Schema.Fields.Values.First(x => x.Name == command.Name).Id, s.Version));
context.Succeed(EntityCreatedResult.Create(s.Schema.Fields.Values.First(x => x.Name == command.Name).Id, s.Version));
});
}

9
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using NSwag.Annotations;
using Squidex.Controllers.Api;
using Squidex.Controllers.ContentApi.Models;
using Squidex.Core.Contents;
using Squidex.Core.Identity;
@ -122,10 +121,12 @@ namespace Squidex.Controllers.ContentApi
var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<Guid>>().IdOrValue;
var response = new EntityCreatedDto { Id = result.ToString() };
var result = context.Result<EntityCreatedResult<ContentData>>();
var response = ContentDto.Create(command, result);
return CreatedAtAction(nameof(GetContent), new { id = result }, response);
Response.Headers["ETag"] = new StringValues(response.Version.ToString());
return CreatedAtAction(nameof(GetContent), new { id = response.Id }, response);
}
[HttpPut]

31
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -44,7 +44,6 @@ namespace Squidex.Controllers.ContentApi.Generator
private readonly string schemaBodyDescription;
private HashSet<Language> languages;
private JsonSchema4 errorDtoSchema;
private JsonSchema4 entityCreatedDtoSchema;
private string appBasePath;
private IAppEntity app;
@ -137,11 +136,6 @@ namespace Squidex.Controllers.ContentApi.Generator
var errorSchema = JsonObjectTypeDescription.FromType(errorType, new Attribute[0], EnumHandling.String);
errorDtoSchema = await swaggerGenerator.GenerateAndAppendSchemaFromTypeAsync(errorType, errorSchema.IsNullable, null);
var entityCreatedType = typeof(EntityCreatedDto);
var entityCreatedSchema = JsonObjectTypeDescription.FromType(entityCreatedType, new Attribute[0], EnumHandling.String);
entityCreatedDtoSchema = await swaggerGenerator.GenerateAndAppendSchemaFromTypeAsync(entityCreatedType, entityCreatedSchema.IsNullable, null);
}
private void GenerateSecurityRequirements()
@ -269,8 +263,10 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.Summary = $"Create a {schemaName} content.";
var responseSchema = CreateContentSchema(schemaName, schemaIdentifier, dataSchema);
operation.AddBodyParameter(dataSchema, "data", schemaBodyDescription);
operation.AddResponse("201", $"{schemaName} created.", entityCreatedDtoSchema);
operation.AddResponse("201", $"{schemaName} created.", responseSchema);
});
}
@ -380,22 +376,19 @@ namespace Squidex.Controllers.ContentApi.Generator
private JsonSchema4 CreateContentSchema(string schemaName, string schemaIdentifier, JsonSchema4 dataSchema)
{
var CreateProperty =
new Func<string, string, JsonProperty>((d, f) =>
new JsonProperty { Description = d, Format = f, IsRequired = true, Type = JsonObjectType.String });
var dataProperty = new JsonProperty { Description = schemaBodyDescription, Type = JsonObjectType.Object, IsRequired = true, SchemaReference = dataSchema };
var schema = new JsonSchema4
{
Properties =
{
["id"] = CreateProperty($"The id of the {schemaName} content.", null),
["id"] = CreateProperty($"The id of the {schemaName} content."),
["data"] = dataProperty,
["version"] = CreateProperty($"The version of the {schemaName}", JsonObjectType.Number),
["created"] = CreateProperty($"The date and time when the {schemaName} content has been created.", "date-time"),
["createdBy"] = CreateProperty($"The user that has created the {schemaName} content.", null),
["createdBy"] = CreateProperty($"The user that has created the {schemaName} content."),
["lastModified"] = CreateProperty($"The date and time when the {schemaName} content has been modified last.", "date-time"),
["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content last.", null)
["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content last.")
},
Type = JsonObjectType.Object
};
@ -403,6 +396,16 @@ namespace Squidex.Controllers.ContentApi.Generator
return AppendSchema($"{schemaIdentifier}ContentDto", schema);
}
private static JsonProperty CreateProperty(string description, JsonObjectType type)
{
return new JsonProperty { Description = description, IsRequired = true, Type = type };
}
private static JsonProperty CreateProperty(string description, string format = null)
{
return new JsonProperty { Description = description, Format = format, IsRequired = true, Type = JsonObjectType.String };
}
private JsonSchema4 AppendSchema(string name, JsonSchema4 schema)
{
name = char.ToUpperInvariant(name[0]) + name.Substring(1);

23
src/Squidex/Controllers/ContentApi/Models/ContentDto.cs

@ -9,7 +9,10 @@
using System;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Write.Contents.Commands;
namespace Squidex.Controllers.ContentApi.Models
{
@ -56,6 +59,24 @@ namespace Squidex.Controllers.ContentApi.Models
/// <summary>
/// The version of the content.
/// </summary>
public int Version { get; set; }
public long Version { get; set; }
public static ContentDto Create(CreateContent command, EntityCreatedResult<ContentData> result)
{
var now = SystemClock.Instance.GetCurrentInstant();
var response = new ContentDto
{
Id = command.ContentId,
Data = result.IdOrValue,
Version = result.Version,
Created = now,
CreatedBy = command.Actor,
LastModified = now,
LastModifiedBy = command.Actor
};
return response;
}
}
}

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

@ -102,7 +102,7 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
this.appName()
.switchMap(app => this.contentsService.postContent(app, this.schema.name, data, this.version))
.subscribe(created => {
this.messageBus.publish(new ContentCreated(created.id, data, this.version.value));
this.messageBus.publish(new ContentCreated(created.id, created.data, this.version.value));
this.router.navigate(['../'], { relativeTo: this.route });
}, error => {

3
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -143,6 +143,9 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
.switchMap(app => this.contentsService.deleteContent(app, this.schema.name, content.id, content.version))
.subscribe(() => {
this.contentItems = this.contentItems.removeAll(x => x.id === content.id);
this.contentTotal--;
this.updatePaging();
this.messageBus.publish(new ContentDeleted(content.id));
}, error => {

17
src/Squidex/app/framework/angular/http-utils.ts

@ -54,14 +54,17 @@ export function catchError(message: string): Observable<any> {
let result = new ErrorDto(500, message);
if (error instanceof Response) {
const body = error.json();
if (error.status === 412) {
result = new ErrorDto(error.status, 'Failed to make the update. Another user has made a change. Please reload.');
} else if (error.status !== 500) {
result = new ErrorDto(error.status, body.message, body.details);
try {
const body = error.json();
if (error.status === 412) {
result = new ErrorDto(error.status, 'Failed to make the update. Another user has made a change. Please reload.');
} else if (error.status !== 500) {
result = new ErrorDto(error.status, body.message, body.details);
}
} catch (e) {
result = result;
}
}
return Observable.throw(result);

22
src/Squidex/app/shared/services/contents.service.spec.ts

@ -12,7 +12,6 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
EntityCreatedDto,
ContentDto,
ContentsDto,
ContentsService,
@ -173,21 +172,32 @@ describe('ContentsService', () => {
new Response(
new ResponseOptions({
body: {
id: 'content1'
id: 'id1',
isPublished: true,
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
version: 11,
data: {}
}
})
)
))
.verifiable(Times.once());
let created: EntityCreatedDto | null = null;
let content: ContentDto | null = null;
contentsService.postContent('my-app', 'my-schema', dto, version).subscribe(result => {
created = result;
content = result;
});
expect(created).toEqual(
new EntityCreatedDto('content1'));
expect(content).toEqual(
new ContentDto('id1', true, 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
{},
new Version('11')));
authService.verifyAll();
});

13
src/Squidex/app/shared/services/contents.service.ts

@ -13,7 +13,6 @@ import 'framework/angular/http-extensions';
import {
ApiUrlConfig,
DateTime,
EntityCreatedDto,
Version
} from 'framework';
@ -111,13 +110,21 @@ export class ContentsService {
.catchError('Failed to load content. Please reload.');
}
public postContent(appName: string, schemaName: string, dto: any, version: Version): Observable<EntityCreatedDto> {
public postContent(appName: string, schemaName: string, dto: any, version: Version): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
.map(response => {
return new EntityCreatedDto(response.id);
return new ContentDto(
response.id,
response.isPublished,
response.createdBy,
response.lastModifiedBy,
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified),
response.data,
new Version(response.version.toString()));
})
.catchError('Failed to create content. Please reload.');
}

5
tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Read.Apps;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Users;
@ -74,7 +75,7 @@ namespace Squidex.Write.Apps
await sut.HandleAsync(context);
});
Assert.Equal(AppId, context.Result<Guid>());
Assert.Equal(AppId, context.Result<EntityCreatedResult<Guid>>().IdOrValue);
}
[Fact]
@ -154,7 +155,7 @@ namespace Squidex.Write.Apps
keyGenerator.VerifyAll();
context.Result<AppClient>().ShouldBeEquivalentTo(new AppClient(clientName, clientSecret));
context.Result<EntityCreatedResult<AppClient>>().IdOrValue.ShouldBeEquivalentTo(new AppClient(clientName, clientSecret));
}
[Fact]

3
tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs

@ -12,6 +12,7 @@ using Moq;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Read.Apps;
using Squidex.Read.Apps.Services;
using Squidex.Read.Schemas;
@ -73,6 +74,8 @@ namespace Squidex.Write.Contents
{
await sut.HandleAsync(context);
});
Assert.Equal(data, context.Result<EntityCreatedResult<ContentData>>().IdOrValue);
}
[Fact]

3
tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Moq;
using Squidex.Core.Schemas;
@ -66,6 +67,8 @@ namespace Squidex.Write.Schemas
{
await sut.HandleAsync(context);
});
Assert.Equal(SchemaId, context.Result<EntityCreatedResult<Guid>>().IdOrValue);
}
[Fact]

Loading…
Cancel
Save