From 41a49ca108aa11c6b179eefcc6f66db51f235d58 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 10 Jun 2019 11:41:40 +0200 Subject: [PATCH] Continued with assets. --- .../Assets/AssetCommandMiddleware.cs | 22 +- .../Assets/AssetCreatedResult.cs | 20 +- .../Assets/AssetGrain.cs | 31 +- .../Assets/AssetSavedResult.cs | 25 -- .../Controllers/Assets/AssetsController.cs | 30 +- .../Assets/Models/AssetCreatedDto.cs | 108 ------ .../Api/Controllers/Assets/Models/AssetDto.cs | 45 ++- .../Assets/Models/AssetMetadata.cs | 17 + .../Assets/Models/AssetReplacedDto.cs | 74 ---- .../Controllers/Assets/Models/AssetsDto.cs | 30 +- .../pages/users/user-page.component.html | 2 +- .../pages/users/user-page.component.ts | 8 +- .../framework/angular/http/http-extensions.ts | 6 + src/Squidex/app/framework/utils/hateos.ts | 24 +- .../components/asset-dialog.component.html | 2 +- .../components/asset-dialog.component.ts | 19 +- .../shared/components/asset.component.html | 4 +- .../app/shared/components/asset.component.ts | 8 +- .../components/markdown-editor.component.ts | 4 +- src/Squidex/app/shared/components/pipes.ts | 14 +- .../components/rich-editor.component.ts | 6 +- .../shared/services/assets.service.spec.ts | 357 ++++++------------ .../app/shared/services/assets.service.ts | 176 ++++----- src/Squidex/app/shared/state/_test-helpers.ts | 3 + .../shared/state/asset-uploader.state.spec.ts | 81 ++-- .../app/shared/state/asset-uploader.state.ts | 69 +--- .../app/shared/state/assets.state.spec.ts | 39 +- src/Squidex/app/shared/state/assets.state.ts | 2 +- .../Assets/AssetGrainTests.cs | 10 +- 29 files changed, 447 insertions(+), 789 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs delete mode 100644 src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Assets/Models/AssetMetadata.cs delete mode 100644 src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index 5c93059f2..b13c99f29 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -70,13 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { if (IsDuplicate(createAsset, existing)) { - result = new AssetCreatedResult( - existing.Id, - existing.Tags, - existing.Version, - existing.FileVersion, - existing.FileHash, - true); + result = new AssetCreatedResult(existing, true); } break; @@ -89,17 +83,11 @@ namespace Squidex.Domain.Apps.Entities.Assets tagGenerator.GenerateTags(createAsset, createAsset.Tags); } - var commandResult = (AssetSavedResult)await ExecuteCommandAsync(createAsset); + var asset = (IAssetEntity)await ExecuteCommandAsync(createAsset); - result = new AssetCreatedResult( - createAsset.AssetId, - createAsset.Tags, - commandResult.Version, - commandResult.FileVersion, - commandResult.FileHash, - false); + result = new AssetCreatedResult(asset, false); - await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), result.FileVersion, null); + await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null); } context.Complete(result); @@ -119,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Assets updateAsset.FileHash = await UploadAsync(context, updateAsset.File); try { - var result = (AssetSavedResult)await ExecuteCommandAsync(updateAsset); + var result = (IAssetEntity)await ExecuteCommandAsync(updateAsset); context.Complete(result); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs index de8da5f23..b1502786a 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs @@ -5,29 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections.Generic; -using Squidex.Infrastructure.Commands; - namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetCreatedResult : EntityCreatedResult + public sealed class AssetCreatedResult { - public HashSet Tags { get; } - - public long FileVersion { get; } - - public string FileHash { get; } + public IAssetEntity Asset { get; } public bool IsDuplicate { get; } - public AssetCreatedResult(Guid id, HashSet tags, long version, long fileVersion, string fileHash, bool isDuplicate) - : base(id, version) + public AssetCreatedResult(IAssetEntity asset, bool isDuplicate) { - Tags = tags; - - FileVersion = fileVersion; - FileHash = fileHash; + Asset = asset; IsDuplicate = isDuplicate; } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index b16b18ae4..19ff817a0 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -51,16 +51,27 @@ namespace Squidex.Domain.Apps.Entities.Assets Create(c, tagIds); - return new AssetSavedResult(Version, Snapshot.FileVersion, Snapshot.FileHash); + return await GetRawStateAsync(); }); case UpdateAsset updateRule: - return UpdateAsync(updateRule, c => + return UpdateReturnAsync(updateRule, async c => { GuardAsset.CanUpdate(c); Update(c); - return new AssetSavedResult(Version, Snapshot.FileVersion, Snapshot.FileHash); + return await GetRawStateAsync(); + }); + case AnnotateAsset annotateAsset: + return UpdateReturnAsync(annotateAsset, async c => + { + GuardAsset.CanAnnotate(c, Snapshot.FileName, Snapshot.Slug); + + var tagIds = await NormalizeTagsAsync(Snapshot.AppId.Id, c.Tags); + + Annotate(c, tagIds); + + return await GetRawStateAsync(); }); case DeleteAsset deleteAsset: return UpdateAsync(deleteAsset, async c => @@ -71,15 +82,6 @@ namespace Squidex.Domain.Apps.Entities.Assets Delete(c); }); - case AnnotateAsset annotateAsset: - return UpdateAsync(annotateAsset, async c => - { - GuardAsset.CanAnnotate(c, Snapshot.FileName, Snapshot.Slug); - - var tagIds = await NormalizeTagsAsync(Snapshot.AppId.Id, c.Tags); - - Annotate(c, tagIds); - }); default: throw new NotSupportedException(); } @@ -163,6 +165,11 @@ namespace Squidex.Domain.Apps.Entities.Assets } } + public Task GetRawStateAsync() + { + return Task.FromResult(Snapshot); + } + public Task> GetStateAsync(long version = EtagVersion.Any) { return J.AsTask(GetSnapshot(version)); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs deleted file mode 100644 index a43e109cc..000000000 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure.Commands; - -namespace Squidex.Domain.Apps.Entities.Assets -{ - public class AssetSavedResult : EntitySavedResult - { - public long FileVersion { get; } - - public string FileHash { get; } - - public AssetSavedResult(long version, long fileVersion, string fileHash) - : base(version) - { - FileVersion = fileVersion; - FileHash = fileHash; - } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 041adcdde..9b0a57b95 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -105,7 +105,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var assets = await assetQuery.QueryAsync(context, Q.Empty.WithODataQuery(Request.QueryString.ToString()).WithIds(ids)); - var response = AssetsDto.FromAssets(assets); + var response = AssetsDto.FromAssets(assets, this, app); if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys) { @@ -142,7 +142,7 @@ namespace Squidex.Areas.Api.Controllers.Assets return NotFound(); } - var response = AssetDto.FromAsset(entity); + var response = AssetDto.FromAsset(entity, this, app); if (controllerOptions.Value.EnableSurrogateKeys) { @@ -169,8 +169,7 @@ namespace Squidex.Areas.Api.Controllers.Assets /// [HttpPost] [Route("apps/{app}/assets/")] - [ProducesResponseType(typeof(AssetCreatedDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] + [ProducesResponseType(typeof(AssetDto), 200)] [AssetRequestSizeLimit] [ApiPermission(Permissions.AppAssetsCreate)] [ApiCosts(1)] @@ -182,7 +181,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var context = await CommandBus.PublishAsync(command); var result = context.Result(); - var response = AssetCreatedDto.FromCommand(command, result); + var response = AssetDto.FromAsset(result.Asset, this, app, result.IsDuplicate); return StatusCode(201, response); } @@ -194,7 +193,7 @@ namespace Squidex.Areas.Api.Controllers.Assets /// The id of the asset. /// The file to upload. /// - /// 201 => Asset updated. + /// 200 => Asset updated. /// 404 => Asset or app not found. /// 400 => Asset exceeds the maximum size. /// @@ -203,8 +202,7 @@ namespace Squidex.Areas.Api.Controllers.Assets /// [HttpPut] [Route("apps/{app}/assets/{id}/content/")] - [ProducesResponseType(typeof(AssetReplacedDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] + [ProducesResponseType(typeof(AssetDto), 200)] [ApiPermission(Permissions.AppAssetsUpdate)] [ApiCosts(1)] public async Task PutAssetContent(string app, Guid id, [SwaggerIgnore] List file) @@ -214,10 +212,10 @@ namespace Squidex.Areas.Api.Controllers.Assets var command = new UpdateAsset { File = assetFile, AssetId = id }; var context = await CommandBus.PublishAsync(command); - var result = context.Result(); - var response = AssetReplacedDto.FromCommand(command, result); + var result = context.Result(); + var response = AssetDto.FromAsset(result, this, app); - return StatusCode(201, response); + return Ok(response); } /// @@ -233,15 +231,19 @@ namespace Squidex.Areas.Api.Controllers.Assets /// [HttpPut] [Route("apps/{app}/assets/{id}/")] - [ProducesResponseType(typeof(ErrorDto), 400)] + [ProducesResponseType(typeof(AssetDto), 200)] [AssetRequestSizeLimit] [ApiPermission(Permissions.AppAssetsUpdate)] [ApiCosts(1)] public async Task PutAsset(string app, Guid id, [FromBody] AnnotateAssetDto request) { - await CommandBus.PublishAsync(request.ToCommand(id)); + var command = request.ToCommand(id); + var context = await CommandBus.PublishAsync(command); - return NoContent(); + var result = context.Result(); + var response = AssetDto.FromAsset(result, this, app); + + return Ok(response); } /// diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs deleted file mode 100644 index da76057c0..000000000 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs +++ /dev/null @@ -1,108 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Domain.Apps.Entities.Assets.Commands; -using Squidex.Infrastructure; - -namespace Squidex.Areas.Api.Controllers.Assets.Models -{ - public sealed class AssetCreatedDto - { - /// - /// The id of the asset. - /// - public Guid Id { get; set; } - - /// - /// The file type. - /// - [Required] - public string FileType { get; set; } - - /// - /// The file name. - /// - [Required] - public string FileName { get; set; } - - /// - /// The slug. - /// - [Required] - public string Slug { get; set; } - - /// - /// The mime type. - /// - [Required] - public string MimeType { get; set; } - - /// - /// The default tags. - /// - [Required] - public HashSet Tags { get; set; } - - /// - /// The size of the file in bytes. - /// - public long FileSize { get; set; } - - /// - /// The version of the file. - /// - public long FileVersion { get; set; } - - /// - /// Determines of the created file is an image. - /// - public bool IsImage { get; set; } - - /// - /// The width of the image in pixels if the asset is an image. - /// - public int? PixelWidth { get; set; } - - /// - /// The height of the image in pixels if the asset is an image. - /// - public int? PixelHeight { get; set; } - - /// - /// Indicates if the asset has been already uploaded. - /// - public bool IsDuplicate { get; set; } - - /// - /// The version of the asset. - /// - public long Version { get; set; } - - public static AssetCreatedDto FromCommand(CreateAsset command, AssetCreatedResult result) - { - return new AssetCreatedDto - { - Id = result.IdOrValue, - FileName = command.File.FileName, - FileSize = command.File.FileSize, - FileType = command.File.FileName.FileType(), - FileVersion = result.FileVersion, - MimeType = command.File.MimeType, - IsImage = command.ImageInfo != null, - IsDuplicate = result.IsDuplicate, - PixelWidth = command.ImageInfo?.PixelWidth, - PixelHeight = command.ImageInfo?.PixelHeight, - Tags = result.Tags, - Version = result.Version - }; - } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index 1053302be..67d292a78 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs @@ -8,15 +8,17 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; using NodaTime; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models { - public sealed class AssetDto : IGenerateETag + public sealed class AssetDto : Resource, IGenerateETag { /// /// The id of the asset. @@ -110,9 +112,46 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models /// public long Version { get; set; } - public static AssetDto FromAsset(IAssetEntity asset) + [JsonProperty("_meta")] + public AssetMetadata Meta { get; set; } + + public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, bool isDuplicate = false) + { + var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); + + if (isDuplicate) + { + response.Meta = new AssetMetadata { IsDuplicate = "true" }; + } + + return CreateLinks(response, controller, app); + } + + private static AssetDto CreateLinks(AssetDto response, ApiController controller, string app) { - return SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); + var values = new { app, id = response.Id }; + + response.AddSelfLink(controller.Url(x => nameof(x.GetAsset), values)); + + if (controller.HasPermission(Permissions.AppAssetsUpdate)) + { + response.AddPutLink("update", controller.Url(x => nameof(x.PutAsset), values)); + response.AddPutLink("upload", controller.Url(x => nameof(x.PutAssetContent), values)); + } + + if (controller.HasPermission(Permissions.AppAssetsDelete)) + { + response.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteAsset), values)); + } + + response.AddGetLink("content", controller.Url(x => nameof(x.GetAssetContent), new { id = response.Id, version = response.FileVersion })); + + if (!string.IsNullOrWhiteSpace(response.Slug)) + { + response.AddGetLink("content/slug", controller.Url(x => nameof(x.GetAssetContentBySlug), new { app, idOrSlug = response.Slug, version = response.Version })); + } + + return response; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetMetadata.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetMetadata.cs new file mode 100644 index 000000000..71f8e9065 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetMetadata.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Areas.Api.Controllers.Assets.Models +{ + public sealed class AssetMetadata + { + /// + /// Indicates whether the asset is a duplicate. + /// + public string IsDuplicate { get; set; } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs deleted file mode 100644 index e65faf494..000000000 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs +++ /dev/null @@ -1,74 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.ComponentModel.DataAnnotations; -using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Domain.Apps.Entities.Assets.Commands; - -namespace Squidex.Areas.Api.Controllers.Assets.Models -{ - public sealed class AssetReplacedDto - { - /// - /// The mime type. - /// - [Required] - public string MimeType { get; set; } - - /// - /// The file hash. - /// - [Required] - public string FileHash { get; set; } - - /// - /// The size of the file in bytes. - /// - public long FileSize { get; set; } - - /// - /// The version of the file. - /// - public long FileVersion { get; set; } - - /// - /// Determines of the created file is an image. - /// - public bool IsImage { get; set; } - - /// - /// The width of the image in pixels if the asset is an image. - /// - public int? PixelWidth { get; set; } - - /// - /// The height of the image in pixels if the asset is an image. - /// - public int? PixelHeight { get; set; } - - /// - /// The version of the asset. - /// - public long Version { get; set; } - - public static AssetReplacedDto FromCommand(UpdateAsset command, AssetSavedResult result) - { - var response = new AssetReplacedDto - { - FileSize = command.File.FileSize, - FileVersion = result.FileVersion, - MimeType = command.File.MimeType, - IsImage = command.ImageInfo != null, - PixelWidth = command.ImageInfo?.PixelWidth, - PixelHeight = command.ImageInfo?.PixelHeight, - Version = result.Version - }; - - return response; - } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs index 6e81fa113..4463dbfd7 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs @@ -9,10 +9,12 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; +using Squidex.Shared; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Assets.Models { - public sealed class AssetsDto + public sealed class AssetsDto : Resource { /// /// The assets. @@ -25,9 +27,31 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models /// public long Total { get; set; } - public static AssetsDto FromAssets(IResultList assets) + public static AssetsDto FromAssets(IResultList assets, ApiController controller, string app) { - return new AssetsDto { Total = assets.Total, Items = assets.Select(AssetDto.FromAsset).ToArray() }; + var response = new AssetsDto + { + Total = assets.Total, + Items = assets.Select(x => AssetDto.FromAsset(x, controller, app)).ToArray() + }; + + return CreateLinks(response, controller, app); + } + + private static AssetsDto CreateLinks(AssetsDto response, ApiController controller, string app) + { + var values = new { app }; + + response.AddSelfLink(controller.Url(x => nameof(x.GetAssets), values)); + + if (controller.HasPermission(Permissions.AppAssetsCreate)) + { + response.AddPostLink("create", controller.Url(x => nameof(x.PostAsset), values)); + } + + response.AddDeleteLink("tags", controller.Url(x => nameof(x.GetTags), values)); + + return response; } } } diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.html b/src/Squidex/app/features/administration/pages/users/user-page.component.html index 44a638173..673c8958e 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.html @@ -16,7 +16,7 @@ - + diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 53a9ac15e..0abdd7eed 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -29,6 +29,8 @@ export class UserPageComponent extends ResourceOwner implements OnInit { public user?: UserDto; public userForm = new UserForm(this.formBuilder); + public isReadOnly = false; + constructor( public readonly usersState: UsersState, private readonly formBuilder: FormBuilder, @@ -47,7 +49,9 @@ export class UserPageComponent extends ResourceOwner implements OnInit { if (selectedUser) { this.userForm.load(selectedUser); - if (!hasLink(selectedUser, 'update')) { + this.isReadOnly = !hasLink(this.user, 'update'); + + if (this.isReadOnly) { this.userForm.form.disable(); } } @@ -55,7 +59,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { } public save() { - if (this.userForm.form.disabled) { + if (this.isReadOnly) { return; } diff --git a/src/Squidex/app/framework/angular/http/http-extensions.ts b/src/Squidex/app/framework/angular/http/http-extensions.ts index 0745c8227..befea290b 100644 --- a/src/Squidex/app/framework/angular/http/http-extensions.ts +++ b/src/Squidex/app/framework/angular/http/http-extensions.ts @@ -47,6 +47,12 @@ export module HTTP { return handleVersion(http.delete(url, { observe: 'response', headers })); } + export function requestVersioned(http: HttpClient, method: string, url: string, version?: Version, body?: any): Observable>> { + const headers = createHeaders(version); + + return handleVersion(http.request(method, url, { observe: 'response', headers, body })); + } + function createHeaders(version?: Version): HttpHeaders { if (version && version.value && version.value.length > 0) { return new HttpHeaders().set('If-Match', version.value); diff --git a/src/Squidex/app/framework/utils/hateos.ts b/src/Squidex/app/framework/utils/hateos.ts index c1c2099c7..924db4eb0 100644 --- a/src/Squidex/app/framework/utils/hateos.ts +++ b/src/Squidex/app/framework/utils/hateos.ts @@ -6,14 +6,22 @@ */ export interface Resource { - readonly _links: { [rel: string]: ResourceLink }; + _links: ResourceLinks; + + _meta?: Metadata; } export type ResourceLinks = { [rel: string]: ResourceLink }; export type ResourceLink = { href: string; method: ResourceMethod; }; +export type Metadata = { [rel: string]: string }; + export function withLinks(value: T, source: Resource) { if (value._links && source._links) { + if (!value._links) { + value._links = {}; + } + for (let key in source._links) { if (source._links.hasOwnProperty(key)) { value._links[key] = source._links[key]; @@ -23,6 +31,20 @@ export function withLinks(value: T, source: Resource) { Object.freeze(value._links); } + if (source._meta) { + if (!value._meta) { + value._meta = {}; + } + + for (let key in source._meta) { + if (source._meta.hasOwnProperty(key)) { + value._meta[key] = source._meta[key]; + } + } + + Object.freeze(value._meta); + } + return value; } diff --git a/src/Squidex/app/shared/components/asset-dialog.component.html b/src/Squidex/app/shared/components/asset-dialog.component.html index 3be944486..37b08bc02 100644 --- a/src/Squidex/app/shared/components/asset-dialog.component.html +++ b/src/Squidex/app/shared/components/asset-dialog.component.html @@ -38,7 +38,7 @@ - + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/asset-dialog.component.ts b/src/Squidex/app/shared/components/asset-dialog.component.ts index f16eeafde..e5d14c0db 100644 --- a/src/Squidex/app/shared/components/asset-dialog.component.ts +++ b/src/Squidex/app/shared/components/asset-dialog.component.ts @@ -13,7 +13,7 @@ import { AppsState, AssetDto, AssetsService, - AuthService, + hasLink, StatefulComponent } from '@app/shared/internal'; @@ -36,12 +36,13 @@ export class AssetDialogComponent extends StatefulComponent implements OnInit { @Output() public complete = new EventEmitter(); + public isReadOnly = false; + public annotateForm = new AnnotateAssetForm(this.formBuilder); constructor(changeDetector: ChangeDetectorRef, private readonly appsState: AppsState, private readonly assetsService: AssetsService, - private readonly authState: AuthService, private readonly formBuilder: FormBuilder ) { super(changeDetector, { @@ -53,6 +54,12 @@ export class AssetDialogComponent extends StatefulComponent implements OnInit { public ngOnInit() { this.annotateForm.load(this.asset); + + this.isReadOnly = !hasLink(this.asset, 'update'); + + if (this.isReadOnly) { + this.annotateForm.form.disable(); + } } public generateSlug() { @@ -68,12 +75,16 @@ export class AssetDialogComponent extends StatefulComponent implements OnInit { } public annotateAsset() { + if (this.isReadOnly) { + return; + } + const value = this.annotateForm.submit(this.asset); if (value) { - this.assetsService.putAsset(this.appsState.appName, this.asset.id, value, this.asset.version) + this.assetsService.putAsset(this.appsState.appName, this.asset, value, this.asset.version) .subscribe(dto => { - this.emitComplete(this.asset.annnotate(value, this.authState.user!.token, dto.version)); + this.emitComplete(dto); }, error => { this.annotateForm.submitFailed(error); }); diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 580e992d4..864304dcf 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -23,7 +23,7 @@ - + @@ -103,7 +103,7 @@ -