From 5c7ae0f653753da751d658aa9355d1055e904119 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 9 Apr 2017 15:54:31 +0200 Subject: [PATCH] Temporary progress --- ...calAssetStorage.cs => FolderAssetStore.cs} | 50 +++++++++------- .../{IAssetStorage.cs => IAssetStore.cs} | 7 +-- .../Assets/IAssetThumbnailGenerator.cs | 2 + .../ImageSharpAssetThumbnailGenerator.cs | 21 ++++++- src/Squidex/Config/Domain/AssetStoreModule.cs | 58 +++++++++++++++++++ .../Config/Domain/EventPublishersModule.cs | 2 +- src/Squidex/Config/Domain/WriteModule.cs | 5 ++ .../Api/Assets/AssetsController.cs | 36 +++++------- src/Squidex/app-config/webpack.config.js | 11 +++- src/Squidex/app/app.routes.ts | 3 + .../app/features/assets/declarations.ts | 8 +++ src/Squidex/app/features/assets/index.ts | 9 +++ src/Squidex/app/features/assets/module.ts | 33 +++++++++++ .../assets/pages/assets-page.component.html | 19 ++++++ .../assets/pages/assets-page.component.scss | 2 + .../assets/pages/assets-page.component.ts | 31 ++++++++++ .../pages/dashboard-page.component.ts | 4 +- src/Squidex/app/shared/declarations.ts | 1 + src/Squidex/app/shared/module.ts | 2 + .../app/shared/services/assets.service.ts | 52 +++++++++++++++++ .../shell/pages/app/left-menu.component.html | 5 ++ src/Squidex/appsettings.json | 6 ++ 22 files changed, 313 insertions(+), 54 deletions(-) rename src/Squidex.Infrastructure/Assets/{Physical/PhysicalAssetStorage.cs => FolderAssetStore.cs} (57%) rename src/Squidex.Infrastructure/Assets/{IAssetStorage.cs => IAssetStore.cs} (70%) create mode 100644 src/Squidex/Config/Domain/AssetStoreModule.cs create mode 100644 src/Squidex/app/features/assets/declarations.ts create mode 100644 src/Squidex/app/features/assets/index.ts create mode 100644 src/Squidex/app/features/assets/module.ts create mode 100644 src/Squidex/app/features/assets/pages/assets-page.component.html create mode 100644 src/Squidex/app/features/assets/pages/assets-page.component.scss create mode 100644 src/Squidex/app/features/assets/pages/assets-page.component.ts create mode 100644 src/Squidex/app/shared/services/assets.service.ts diff --git a/src/Squidex.Infrastructure/Assets/Physical/PhysicalAssetStorage.cs b/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs similarity index 57% rename from src/Squidex.Infrastructure/Assets/Physical/PhysicalAssetStorage.cs rename to src/Squidex.Infrastructure/Assets/FolderAssetStore.cs index 30945aa3a..1916cfc37 100644 --- a/src/Squidex.Infrastructure/Assets/Physical/PhysicalAssetStorage.cs +++ b/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs @@ -1,24 +1,28 @@ // ========================================================================== -// PhysicalAssetStorage.cs +// FolderAssetStorage.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using System; using System.IO; using System.Threading.Tasks; +using Squidex.Infrastructure.Log; -namespace Squidex.Infrastructure.Assets.Physical +namespace Squidex.Infrastructure.Assets { - public sealed class PhysicalAssetStorage : IAssetStorage, IExternalSystem + public sealed class FolderAssetStore : IAssetStore, IExternalSystem { + private readonly ISemanticLog log; private readonly DirectoryInfo directory; - public PhysicalAssetStorage(string path) + public FolderAssetStore(string path, ISemanticLog log) { Guard.NotNullOrEmpty(path, nameof(path)); + Guard.NotNull(log, nameof(log)); + + this.log = log; directory = new DirectoryInfo(path); } @@ -31,6 +35,10 @@ namespace Squidex.Infrastructure.Assets.Physical { directory.Create(); } + + log.LogInformation(w => w + .WriteProperty("action", "FolderAssetStoreConfigured") + .WriteProperty("path", directory.FullName)); } catch { @@ -41,23 +49,30 @@ namespace Squidex.Infrastructure.Assets.Physical } } - public Task GetAssetAsync(Guid id, string tags = null) + public Task GetAssetAsync(string name) { - var file = GetFile(id, tags); + var file = GetFile(name); Stream stream = null; - if (file.Exists) + try { - stream = file.OpenRead(); + if (file.Exists) + { + stream = file.OpenRead(); + } + } + catch (FileNotFoundException) + { + stream = null; } return Task.FromResult(stream); } - public async Task UploadAssetAsync(Guid id, Stream stream, string tags = null) + public async Task UploadAssetAsync(string name, Stream stream) { - var file = GetFile(id, tags); + var file = GetFile(name); using (var fileStream = file.OpenWrite()) { @@ -65,18 +80,11 @@ namespace Squidex.Infrastructure.Assets.Physical } } - private FileInfo GetFile(Guid id, string tags) + private FileInfo GetFile(string name) { - var fileName = id.ToString(); - - if (!string.IsNullOrWhiteSpace(tags)) - { - fileName += tags; - } - - Guard.ValidFileName(fileName, tags); + Guard.ValidFileName(name, nameof(name)); - var file = new FileInfo(Path.Combine(directory.FullName, fileName)); + var file = new FileInfo(Path.Combine(directory.FullName, name)); return file; } diff --git a/src/Squidex.Infrastructure/Assets/IAssetStorage.cs b/src/Squidex.Infrastructure/Assets/IAssetStore.cs similarity index 70% rename from src/Squidex.Infrastructure/Assets/IAssetStorage.cs rename to src/Squidex.Infrastructure/Assets/IAssetStore.cs index c152601cc..bf296eacc 100644 --- a/src/Squidex.Infrastructure/Assets/IAssetStorage.cs +++ b/src/Squidex.Infrastructure/Assets/IAssetStore.cs @@ -6,16 +6,15 @@ // All rights reserved. // ========================================================================== -using System; using System.IO; using System.Threading.Tasks; namespace Squidex.Infrastructure.Assets { - public interface IAssetStorage + public interface IAssetStore { - Task GetAssetAsync(Guid id, string tags = null); + Task GetAssetAsync(string name); - Task UploadAssetAsync(Guid id, Stream stream, string tags = null); + Task UploadAssetAsync(string name, Stream stream); } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs index c974a877f..f4bede163 100644 --- a/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs +++ b/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs @@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Assets { public interface IAssetThumbnailGenerator { + Task IsValidImageAsync(Stream input); + Task GetThumbnailOrNullAsync(Stream input, int dimension); } } diff --git a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs index f6aa05e3a..fb550abca 100644 --- a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs +++ b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs @@ -24,7 +24,7 @@ namespace Squidex.Infrastructure.Assets.ImageSharp public Task GetThumbnailOrNullAsync(Stream input, int dimension) { - return Task.Run(() => + return Task.Run(() => { var result = new MemoryStream(); @@ -39,7 +39,24 @@ namespace Squidex.Infrastructure.Assets.ImageSharp image.Save(result); - return result; + return (Stream)result; + }); + } + + public Task IsValidImageAsync(Stream input) + { + return Task.Run(() => + { + try + { + var image = new Image(input); + + return image.Width > 0 && image.Height > 0; + } + catch + { + return false; + } }); } } diff --git a/src/Squidex/Config/Domain/AssetStoreModule.cs b/src/Squidex/Config/Domain/AssetStoreModule.cs new file mode 100644 index 000000000..2ccd20a5c --- /dev/null +++ b/src/Squidex/Config/Domain/AssetStoreModule.cs @@ -0,0 +1,58 @@ +// ========================================================================== +// AssetStoreModule.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Autofac; +using Microsoft.Extensions.Configuration; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Assets; +using Squidex.Infrastructure.Log; + +// ReSharper disable InvertIf + +namespace Squidex.Config.Domain +{ + public sealed class AssetStoreModule : Module + { + private IConfiguration Configuration { get; } + + public AssetStoreModule(IConfiguration configuration) + { + Configuration = configuration; + } + + protected override void Load(ContainerBuilder builder) + { + var assetStoreType = Configuration.GetValue("assetStore:type"); + + if (string.IsNullOrWhiteSpace(assetStoreType)) + { + throw new ConfigurationException("Configure the AssetStore type with 'assetStore:type'."); + } + + if (string.Equals(assetStoreType, "Folder", StringComparison.OrdinalIgnoreCase)) + { + var path = Configuration.GetValue("assetStore:folder:path"); + + if (string.IsNullOrWhiteSpace(path)) + { + throw new ConfigurationException("Configure AssetStore Folder path with 'assetStore:folder:path'."); + } + + builder.Register(c => new FolderAssetStore(path, c.Resolve())) + .As() + .As() + .SingleInstance(); + } + else + { + throw new ConfigurationException($"Unsupported value '{assetStoreType}' for 'assetStore:type', supported: Folder."); + } + } + } +} diff --git a/src/Squidex/Config/Domain/EventPublishersModule.cs b/src/Squidex/Config/Domain/EventPublishersModule.cs index c30f762ca..f96ecb6bb 100644 --- a/src/Squidex/Config/Domain/EventPublishersModule.cs +++ b/src/Squidex/Config/Domain/EventPublishersModule.cs @@ -1,5 +1,5 @@ // ========================================================================== -// RabbitMqModule.cs +// EventPublishersModule.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group diff --git a/src/Squidex/Config/Domain/WriteModule.cs b/src/Squidex/Config/Domain/WriteModule.cs index 66c169c90..3aa01c5f7 100644 --- a/src/Squidex/Config/Domain/WriteModule.cs +++ b/src/Squidex/Config/Domain/WriteModule.cs @@ -12,6 +12,7 @@ using Squidex.Core.Schemas; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Pipeline.CommandHandlers; using Squidex.Write.Apps; +using Squidex.Write.Assets; using Squidex.Write.Contents; using Squidex.Write.Schemas; @@ -78,6 +79,10 @@ namespace Squidex.Config.Domain .AsSelf() .SingleInstance(); + builder.Register>(c => (id => new AssetDomainObject(id, -1))) + .AsSelf() + .SingleInstance(); + builder.Register>(c => (id => new ContentDomainObject(id, -1))) .AsSelf() .SingleInstance(); diff --git a/src/Squidex/Controllers/Api/Assets/AssetsController.cs b/src/Squidex/Controllers/Api/Assets/AssetsController.cs index 083064063..ca6f5869e 100644 --- a/src/Squidex/Controllers/Api/Assets/AssetsController.cs +++ b/src/Squidex/Controllers/Api/Assets/AssetsController.cs @@ -32,20 +32,20 @@ namespace Squidex.Controllers.Api.Assets [SwaggerTag("Assets")] public class AssetsController : ControllerBase { - private readonly IAssetStorage assetStorage; + private readonly IAssetStore assetStorage; + private readonly IAssetThumbnailGenerator assetThumbnailGenerator; private readonly AssetConfig assetsConfig; - private readonly IAssetThumbnailGenerator thumbnailGenerator; public AssetsController( ICommandBus commandBus, - IAssetStorage assetStorage, - IAssetThumbnailGenerator thumbnailGenerator, + IAssetStore assetStorage, + IAssetThumbnailGenerator assetThumbnailGenerator, IOptions assetsConfig) : base(commandBus) { this.assetStorage = assetStorage; + this.assetThumbnailGenerator = assetThumbnailGenerator; this.assetsConfig = assetsConfig.Value; - this.thumbnailGenerator = thumbnailGenerator; } /// @@ -72,32 +72,24 @@ namespace Squidex.Controllers.Api.Assets throw new ValidationException("Cannot create asset.", error); } + var fileContent = new MemoryStream(); + + await file.OpenReadStream().CopyToAsync(fileContent); + + fileContent.Position = 0; + var command = new CreateAsset { AssetId = Guid.NewGuid(), FileSize = file.Length, FileName = file.Name, - MimeType = file.ContentType + MimeType = file.ContentType, + IsImage = await assetThumbnailGenerator.IsValidImageAsync(fileContent) }; - var fileContent = new MemoryStream(); - - await file.CopyToAsync(fileContent); - - fileContent.Position = 0; - - var fileThumbnail = await thumbnailGenerator.GetThumbnailOrNullAsync(fileContent, 200); - - if (fileThumbnail != null) - { - command.IsImage = true; - - await assetStorage.UploadAssetAsync(command.AssetId, fileThumbnail, "thumbnail"); - } - fileContent.Position = 0; - await assetStorage.UploadAssetAsync(command.AssetId, fileContent); + await assetStorage.UploadAssetAsync($"{command.AssetId}_0", fileContent); var context = await CommandBus.PublishAsync(command); diff --git a/src/Squidex/app-config/webpack.config.js b/src/Squidex/app-config/webpack.config.js index 83b8b1f4a..99c739607 100644 --- a/src/Squidex/app-config/webpack.config.js +++ b/src/Squidex/app-config/webpack.config.js @@ -48,9 +48,16 @@ module.exports = { loader: 'angular2-router-loader' }, { loader: 'angular2-template-loader' - },{ + }, { loader: 'tslint-loader' - }] + }], + exclude: /node_modules/ + },{ + test: /\.ts$/, + use: [{ + loader: 'awesome-typescript-loader' + }], + include: /node_modules/ }, { test: /\.html$/, use: [{ diff --git a/src/Squidex/app/app.routes.ts b/src/Squidex/app/app.routes.ts index 819a7c4d9..66e02f0c9 100644 --- a/src/Squidex/app/app.routes.ts +++ b/src/Squidex/app/app.routes.ts @@ -53,6 +53,9 @@ export const routes: Routes = [ }, { path: 'schemas', loadChildren: './features/schemas/module#SqxFeatureSchemasModule' + }, { + path: 'assets', + loadChildren: './features/assets/module#SqxFeatureAssetsModule' }, { path: 'settings', loadChildren: './features/settings/module#SqxFeatureSettingsModule' diff --git a/src/Squidex/app/features/assets/declarations.ts b/src/Squidex/app/features/assets/declarations.ts new file mode 100644 index 000000000..0cc787227 --- /dev/null +++ b/src/Squidex/app/features/assets/declarations.ts @@ -0,0 +1,8 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +export * from './pages/assets-page.component'; \ No newline at end of file diff --git a/src/Squidex/app/features/assets/index.ts b/src/Squidex/app/features/assets/index.ts new file mode 100644 index 000000000..9c0a4f6ee --- /dev/null +++ b/src/Squidex/app/features/assets/index.ts @@ -0,0 +1,9 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +export * from './declarations'; +export * from './module'; \ No newline at end of file diff --git a/src/Squidex/app/features/assets/module.ts b/src/Squidex/app/features/assets/module.ts new file mode 100644 index 000000000..a645c7739 --- /dev/null +++ b/src/Squidex/app/features/assets/module.ts @@ -0,0 +1,33 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { SqxFrameworkModule } from 'shared'; + +import { + AssetsPageComponent +} from './declarations'; + +const routes: Routes = [ + { + path: '', + component: AssetsPageComponent + } +]; + +@NgModule({ + imports: [ + SqxFrameworkModule, + RouterModule.forChild(routes) + ], + declarations: [ + AssetsPageComponent + ] +}) +export class SqxFeatureAssetsModule { } \ No newline at end of file diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.html b/src/Squidex/app/features/assets/pages/assets-page.component.html new file mode 100644 index 000000000..0cf805c7e --- /dev/null +++ b/src/Squidex/app/features/assets/pages/assets-page.component.html @@ -0,0 +1,19 @@ + + + +
+
+

Assets

+
+ + + + +
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.scss b/src/Squidex/app/features/assets/pages/assets-page.component.scss new file mode 100644 index 000000000..fbb752506 --- /dev/null +++ b/src/Squidex/app/features/assets/pages/assets-page.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.ts b/src/Squidex/app/features/assets/pages/assets-page.component.ts new file mode 100644 index 000000000..dea529b8a --- /dev/null +++ b/src/Squidex/app/features/assets/pages/assets-page.component.ts @@ -0,0 +1,31 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component } from '@angular/core'; + +import { + AppComponentBase, + AppsStoreService, + fadeAnimation, + NotificationService, + UsersProviderService + } from 'shared'; + +@Component({ + selector: 'sqx-assets-page', + styleUrls: ['./assets-page.component.scss'], + templateUrl: './assets-page.component.html', + animations: [ + fadeAnimation + ] +}) +export class AssetsPageComponent extends AppComponentBase { + constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) { + super(notifications, users, apps); + } +} + diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts index 936810728..82fd16d64 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { Component } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription} from 'rxjs'; import { @@ -27,7 +27,7 @@ declare var _urq: any; fadeAnimation ] }) -export class DashboardPageComponent extends AppComponentBase { +export class DashboardPageComponent extends AppComponentBase implements OnInit, OnDestroy { private authenticationSubscription: Subscription; public profileDisplayName = ''; diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index 22bb436db..05576450f 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/src/Squidex/app/shared/declarations.ts @@ -26,6 +26,7 @@ export * from './services/app-clients.service'; export * from './services/app-languages.service'; export * from './services/apps-store.service'; export * from './services/apps.service'; +export * from './services/assets.service'; export * from './services/auth.service'; export * from './services/contents.service'; export * from './services/event-consumers.service'; diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index e8ee4bcb9..ad190be09 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -17,6 +17,7 @@ import { AppsStoreService, AppsService, AppMustExistGuard, + AssetsService, AuthService, ContentsService, DashboardLinkDirective, @@ -69,6 +70,7 @@ export class SqxSharedModule { AppsStoreService, AppsService, AppMustExistGuard, + AssetsService, AuthService, ContentsService, EventConsumersService, diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts new file mode 100644 index 000000000..38f8030b3 --- /dev/null +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -0,0 +1,52 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; +import { Observable } from 'rxjs'; + +import { ApiUrlConfig, EntityCreatedDto } from 'framework'; +import { AuthService } from './auth.service'; + +@Injectable() +export class AssetsService { + constructor( + private readonly http: Http, + private readonly apiUrl: ApiUrlConfig, + private readonly authService: AuthService + ) { + } + + public uploadFile(appName: string, file: File): Observable { + return new Observable(subscriber => { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`); + + const content = new FormData(); + const headers = new Headers({ + 'Authorization': `${this.authService.user.user.token_type} ${this.authService.user.user.access_token}`, + 'Content-Type': 'multipart/form-data' + }); + + content.append('file', file); + + this.http + .post(url, content, headers) + .map(response => response.json()) + .map(response => { + return new EntityCreatedDto(response.id); + }) + .catchError('Failed to upload asset. Please reload.') + .subscribe(value => { + subscriber.next(value); + }, err => { + subscriber.error(err); + }, () => { + subscriber.complete(); + }); + }); + } +} \ No newline at end of file diff --git a/src/Squidex/app/shell/pages/app/left-menu.component.html b/src/Squidex/app/shell/pages/app/left-menu.component.html index 68afcf8f1..8eb971eb8 100644 --- a/src/Squidex/app/shell/pages/app/left-menu.component.html +++ b/src/Squidex/app/shell/pages/app/left-menu.component.html @@ -10,6 +10,11 @@ +