Browse Source

Temporary progress

pull/65/head
Sebastian Stehle 10 years ago
parent
commit
5c7ae0f653
  1. 50
      src/Squidex.Infrastructure/Assets/FolderAssetStore.cs
  2. 7
      src/Squidex.Infrastructure/Assets/IAssetStore.cs
  3. 2
      src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs
  4. 21
      src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
  5. 58
      src/Squidex/Config/Domain/AssetStoreModule.cs
  6. 2
      src/Squidex/Config/Domain/EventPublishersModule.cs
  7. 5
      src/Squidex/Config/Domain/WriteModule.cs
  8. 36
      src/Squidex/Controllers/Api/Assets/AssetsController.cs
  9. 11
      src/Squidex/app-config/webpack.config.js
  10. 3
      src/Squidex/app/app.routes.ts
  11. 8
      src/Squidex/app/features/assets/declarations.ts
  12. 9
      src/Squidex/app/features/assets/index.ts
  13. 33
      src/Squidex/app/features/assets/module.ts
  14. 19
      src/Squidex/app/features/assets/pages/assets-page.component.html
  15. 2
      src/Squidex/app/features/assets/pages/assets-page.component.scss
  16. 31
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  17. 4
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  18. 1
      src/Squidex/app/shared/declarations.ts
  19. 2
      src/Squidex/app/shared/module.ts
  20. 52
      src/Squidex/app/shared/services/assets.service.ts
  21. 5
      src/Squidex/app/shell/pages/app/left-menu.component.html
  22. 6
      src/Squidex/appsettings.json

50
src/Squidex.Infrastructure/Assets/Physical/PhysicalAssetStorage.cs → src/Squidex.Infrastructure/Assets/FolderAssetStore.cs

@ -1,24 +1,28 @@
// ========================================================================== // ==========================================================================
// PhysicalAssetStorage.cs // FolderAssetStorage.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.IO; using System.IO;
using System.Threading.Tasks; 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; private readonly DirectoryInfo directory;
public PhysicalAssetStorage(string path) public FolderAssetStore(string path, ISemanticLog log)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(log, nameof(log));
this.log = log;
directory = new DirectoryInfo(path); directory = new DirectoryInfo(path);
} }
@ -31,6 +35,10 @@ namespace Squidex.Infrastructure.Assets.Physical
{ {
directory.Create(); directory.Create();
} }
log.LogInformation(w => w
.WriteProperty("action", "FolderAssetStoreConfigured")
.WriteProperty("path", directory.FullName));
} }
catch catch
{ {
@ -41,23 +49,30 @@ namespace Squidex.Infrastructure.Assets.Physical
} }
} }
public Task<Stream> GetAssetAsync(Guid id, string tags = null) public Task<Stream> GetAssetAsync(string name)
{ {
var file = GetFile(id, tags); var file = GetFile(name);
Stream stream = null; Stream stream = null;
if (file.Exists) try
{ {
stream = file.OpenRead(); if (file.Exists)
{
stream = file.OpenRead();
}
}
catch (FileNotFoundException)
{
stream = null;
} }
return Task.FromResult(stream); 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()) 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(); Guard.ValidFileName(name, nameof(name));
if (!string.IsNullOrWhiteSpace(tags))
{
fileName += tags;
}
Guard.ValidFileName(fileName, tags);
var file = new FileInfo(Path.Combine(directory.FullName, fileName)); var file = new FileInfo(Path.Combine(directory.FullName, name));
return file; return file;
} }

7
src/Squidex.Infrastructure/Assets/IAssetStorage.cs → src/Squidex.Infrastructure/Assets/IAssetStore.cs

@ -6,16 +6,15 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Infrastructure.Assets namespace Squidex.Infrastructure.Assets
{ {
public interface IAssetStorage public interface IAssetStore
{ {
Task<Stream> GetAssetAsync(Guid id, string tags = null); Task<Stream> GetAssetAsync(string name);
Task UploadAssetAsync(Guid id, Stream stream, string tags = null); Task UploadAssetAsync(string name, Stream stream);
} }
} }

2
src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs

@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Assets
{ {
public interface IAssetThumbnailGenerator public interface IAssetThumbnailGenerator
{ {
Task<bool> IsValidImageAsync(Stream input);
Task<Stream> GetThumbnailOrNullAsync(Stream input, int dimension); Task<Stream> GetThumbnailOrNullAsync(Stream input, int dimension);
} }
} }

21
src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs

@ -24,7 +24,7 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
public Task<Stream> GetThumbnailOrNullAsync(Stream input, int dimension) public Task<Stream> GetThumbnailOrNullAsync(Stream input, int dimension)
{ {
return Task.Run<Stream>(() => return Task.Run(() =>
{ {
var result = new MemoryStream(); var result = new MemoryStream();
@ -39,7 +39,24 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
image.Save(result); image.Save(result);
return result; return (Stream)result;
});
}
public Task<bool> IsValidImageAsync(Stream input)
{
return Task.Run(() =>
{
try
{
var image = new Image(input);
return image.Width > 0 && image.Height > 0;
}
catch
{
return false;
}
}); });
} }
} }

58
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<string>("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<string>("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<ISemanticLog>()))
.As<IAssetStore>()
.As<IExternalSystem>()
.SingleInstance();
}
else
{
throw new ConfigurationException($"Unsupported value '{assetStoreType}' for 'assetStore:type', supported: Folder.");
}
}
}
}

2
src/Squidex/Config/Domain/EventPublishersModule.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// RabbitMqModule.cs // EventPublishersModule.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group

5
src/Squidex/Config/Domain/WriteModule.cs

@ -12,6 +12,7 @@ using Squidex.Core.Schemas;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Pipeline.CommandHandlers; using Squidex.Pipeline.CommandHandlers;
using Squidex.Write.Apps; using Squidex.Write.Apps;
using Squidex.Write.Assets;
using Squidex.Write.Contents; using Squidex.Write.Contents;
using Squidex.Write.Schemas; using Squidex.Write.Schemas;
@ -78,6 +79,10 @@ namespace Squidex.Config.Domain
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();
builder.Register<DomainObjectFactoryFunction<AssetDomainObject>>(c => (id => new AssetDomainObject(id, -1)))
.AsSelf()
.SingleInstance();
builder.Register<DomainObjectFactoryFunction<ContentDomainObject>>(c => (id => new ContentDomainObject(id, -1))) builder.Register<DomainObjectFactoryFunction<ContentDomainObject>>(c => (id => new ContentDomainObject(id, -1)))
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();

36
src/Squidex/Controllers/Api/Assets/AssetsController.cs

@ -32,20 +32,20 @@ namespace Squidex.Controllers.Api.Assets
[SwaggerTag("Assets")] [SwaggerTag("Assets")]
public class AssetsController : ControllerBase public class AssetsController : ControllerBase
{ {
private readonly IAssetStorage assetStorage; private readonly IAssetStore assetStorage;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
private readonly AssetConfig assetsConfig; private readonly AssetConfig assetsConfig;
private readonly IAssetThumbnailGenerator thumbnailGenerator;
public AssetsController( public AssetsController(
ICommandBus commandBus, ICommandBus commandBus,
IAssetStorage assetStorage, IAssetStore assetStorage,
IAssetThumbnailGenerator thumbnailGenerator, IAssetThumbnailGenerator assetThumbnailGenerator,
IOptions<AssetConfig> assetsConfig) IOptions<AssetConfig> assetsConfig)
: base(commandBus) : base(commandBus)
{ {
this.assetStorage = assetStorage; this.assetStorage = assetStorage;
this.assetThumbnailGenerator = assetThumbnailGenerator;
this.assetsConfig = assetsConfig.Value; this.assetsConfig = assetsConfig.Value;
this.thumbnailGenerator = thumbnailGenerator;
} }
/// <summary> /// <summary>
@ -72,32 +72,24 @@ namespace Squidex.Controllers.Api.Assets
throw new ValidationException("Cannot create asset.", error); throw new ValidationException("Cannot create asset.", error);
} }
var fileContent = new MemoryStream();
await file.OpenReadStream().CopyToAsync(fileContent);
fileContent.Position = 0;
var command = new CreateAsset var command = new CreateAsset
{ {
AssetId = Guid.NewGuid(), AssetId = Guid.NewGuid(),
FileSize = file.Length, FileSize = file.Length,
FileName = file.Name, 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; fileContent.Position = 0;
await assetStorage.UploadAssetAsync(command.AssetId, fileContent); await assetStorage.UploadAssetAsync($"{command.AssetId}_0", fileContent);
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);

11
src/Squidex/app-config/webpack.config.js

@ -48,9 +48,16 @@ module.exports = {
loader: 'angular2-router-loader' loader: 'angular2-router-loader'
}, { }, {
loader: 'angular2-template-loader' loader: 'angular2-template-loader'
},{ }, {
loader: 'tslint-loader' loader: 'tslint-loader'
}] }],
exclude: /node_modules/
},{
test: /\.ts$/,
use: [{
loader: 'awesome-typescript-loader'
}],
include: /node_modules/
}, { }, {
test: /\.html$/, test: /\.html$/,
use: [{ use: [{

3
src/Squidex/app/app.routes.ts

@ -53,6 +53,9 @@ export const routes: Routes = [
}, { }, {
path: 'schemas', path: 'schemas',
loadChildren: './features/schemas/module#SqxFeatureSchemasModule' loadChildren: './features/schemas/module#SqxFeatureSchemasModule'
}, {
path: 'assets',
loadChildren: './features/assets/module#SqxFeatureAssetsModule'
}, { }, {
path: 'settings', path: 'settings',
loadChildren: './features/settings/module#SqxFeatureSettingsModule' loadChildren: './features/settings/module#SqxFeatureSettingsModule'

8
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';

9
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';

33
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 { }

19
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -0,0 +1,19 @@
<sqx-title message="{app} | Assets" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-panel panelWidth="60rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Assets</h3>
</div>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>
</div>
<div class="panel-main">
<div class="panel-content panel-content-scroll">
</div>
</div>
</sqx-panel>

2
src/Squidex/app/features/assets/pages/assets-page.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

31
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);
}
}

4
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription} from 'rxjs'; import { Subscription} from 'rxjs';
import { import {
@ -27,7 +27,7 @@ declare var _urq: any;
fadeAnimation fadeAnimation
] ]
}) })
export class DashboardPageComponent extends AppComponentBase { export class DashboardPageComponent extends AppComponentBase implements OnInit, OnDestroy {
private authenticationSubscription: Subscription; private authenticationSubscription: Subscription;
public profileDisplayName = ''; public profileDisplayName = '';

1
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/app-languages.service';
export * from './services/apps-store.service'; export * from './services/apps-store.service';
export * from './services/apps.service'; export * from './services/apps.service';
export * from './services/assets.service';
export * from './services/auth.service'; export * from './services/auth.service';
export * from './services/contents.service'; export * from './services/contents.service';
export * from './services/event-consumers.service'; export * from './services/event-consumers.service';

2
src/Squidex/app/shared/module.ts

@ -17,6 +17,7 @@ import {
AppsStoreService, AppsStoreService,
AppsService, AppsService,
AppMustExistGuard, AppMustExistGuard,
AssetsService,
AuthService, AuthService,
ContentsService, ContentsService,
DashboardLinkDirective, DashboardLinkDirective,
@ -69,6 +70,7 @@ export class SqxSharedModule {
AppsStoreService, AppsStoreService,
AppsService, AppsService,
AppMustExistGuard, AppMustExistGuard,
AssetsService,
AuthService, AuthService,
ContentsService, ContentsService,
EventConsumersService, EventConsumersService,

52
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<number | EntityCreatedDto> {
return new Observable<number | EntityCreatedDto>(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();
});
});
}
}

5
src/Squidex/app/shell/pages/app/left-menu.component.html

@ -10,6 +10,11 @@
<i class="nav-icon icon-content"></i> <div class="nav-text">Content</div> <i class="nav-icon icon-content"></i> <div class="nav-text">Content</div>
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" routerLink="assets" routerLinkActive="active">
<i class="nav-icon icon-media"></i> <div class="nav-text">Assets</div>
</a>
</li>
<li class="nav-item" *ngIf="permission === 'Owner'"> <li class="nav-item" *ngIf="permission === 'Owner'">
<a class="nav-link" routerLink="settings" routerLinkActive="active"> <a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="nav-icon icon-settings"></i> <div class="nav-text">Settings</div> <i class="nav-icon icon-settings"></i> <div class="nav-text">Settings</div>

6
src/Squidex/appsettings.json

@ -11,6 +11,12 @@
"configuration": "localhost:6379,resolveDns=1" "configuration": "localhost:6379,resolveDns=1"
} }
}, },
"assetStore": {
"type": "Folder",
"folder": {
"path": "Assets"
}
},
"eventStore": { "eventStore": {
"type": "MongoDb", "type": "MongoDb",
"mongoDb": { "mongoDb": {

Loading…
Cancel
Save