Browse Source

News system finalized.

pull/347/head
Sebastian Stehle 7 years ago
parent
commit
ebb9cfc475
  1. 2
      src/Squidex/Areas/Api/Controllers/News/Models/FeatureDto.cs
  2. 26
      src/Squidex/Areas/Api/Controllers/News/MyNewsOptions.cs
  3. 5
      src/Squidex/Areas/Api/Controllers/News/NewsController.cs
  4. 20
      src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs
  5. 4
      src/Squidex/Config/Domain/InfrastructureServices.cs
  6. 1
      src/Squidex/Squidex.csproj
  7. 3
      src/Squidex/WebStartup.cs
  8. 1
      src/Squidex/app/features/apps/declarations.ts
  9. 2
      src/Squidex/app/features/apps/module.ts
  10. 8
      src/Squidex/app/features/apps/pages/apps-page.component.html
  11. 32
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  12. 17
      src/Squidex/app/features/apps/pages/news-dialog.component.html
  13. 16
      src/Squidex/app/features/apps/pages/news-dialog.component.scss
  14. 27
      src/Squidex/app/features/apps/pages/news-dialog.component.ts
  15. 12
      src/Squidex/app/framework/services/local-store.service.spec.ts
  16. 12
      src/Squidex/app/framework/services/local-store.service.ts
  17. 1
      src/Squidex/app/shared/module.ts
  18. 15
      src/Squidex/app/shared/services/comments.service.ts
  19. 4
      src/Squidex/app/shared/services/news.service.spec.ts
  20. 4
      src/Squidex/app/shared/services/news.service.ts
  21. 12
      src/Squidex/appsettings.json

2
src/Squidex/Areas/Api/Controllers/News/Models/FeatureDto.cs

@ -21,6 +21,6 @@ namespace Squidex.Areas.Api.Controllers.News.Models
/// The description text.
/// </summary>
[Required]
public string Description { get; set; }
public string Text { get; set; }
}
}

26
src/Squidex/Areas/Api/Controllers/News/MyNewsOptions.cs

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Areas.Api.Controllers.News
{
public sealed class MyNewsOptions
{
public string AppName { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public bool IsConfigured()
{
return
!string.IsNullOrWhiteSpace(AppName) &&
!string.IsNullOrWhiteSpace(ClientId) &&
!string.IsNullOrWhiteSpace(ClientSecret);
}
}
}

5
src/Squidex/Areas/Api/Controllers/News/NewsController.cs

@ -20,11 +20,12 @@ namespace Squidex.Areas.Api.Controllers.News
[ApiExplorerSettings(GroupName = nameof(Languages))]
public sealed class NewsController : ApiController
{
private readonly FeaturesService featuresService = new FeaturesService();
private FeaturesService featuresService;
public NewsController(ICommandBus commandBus)
public NewsController(ICommandBus commandBus, FeaturesService featuresService)
: base(commandBus)
{
this.featuresService = featuresService;
}
/// <summary>

20
src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs

@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Squidex.Areas.Api.Controllers.News.Models;
using Squidex.ClientLibrary;
@ -15,9 +16,6 @@ namespace Squidex.Areas.Api.Controllers.News.Service
{
public sealed class FeaturesService
{
private const string AppName = "squidex-website";
private const string ClientId = "squidex-website:default";
private const string ClientSecret = "QGgqxd7bDHBTEkpC6fj8sbdPWgZrPrPfr3xzb3LKoec=";
private const int FeatureVersion = 1;
private static readonly QueryContext Flatten = QueryContext.Default.Flatten();
private readonly SquidexClient<NewsEntity, FeatureDto> client;
@ -26,20 +24,26 @@ namespace Squidex.Areas.Api.Controllers.News.Service
{
}
public FeaturesService()
public FeaturesService(IOptions<MyNewsOptions> options)
{
var clientManager = new SquidexClientManager("https://cloud.squidex.io", AppName, ClientId, ClientSecret);
if (options.Value.IsConfigured())
{
var clientManager = new SquidexClientManager("https://cloud.squidex.io",
options.Value.AppName,
options.Value.ClientId,
options.Value.ClientSecret);
client = clientManager.GetClient<NewsEntity, FeatureDto>("feature-news");
client = clientManager.GetClient<NewsEntity, FeatureDto>("feature-news");
}
}
public async Task<FeaturesDto> GetFeaturesAsync(int version = 0)
{
var result = new FeaturesDto { Features = new List<FeatureDto>(), Version = FeatureVersion };
if (version < FeatureVersion)
if (client != null && version < FeatureVersion)
{
var entities = await client.GetAsync(filter: $"data/version/iv ge ${version}", context: Flatten);
var entities = await client.GetAsync(filter: $"data/version/iv ge {version}", context: Flatten);
result.Features.AddRange(entities.Items.Select(x => x.Data));
}

4
src/Squidex/Config/Domain/InfrastructureServices.cs

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
using Squidex.Areas.Api.Controllers.News.Service;
using Squidex.Domain.Apps.Entities.Apps.Diagnostics;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
@ -36,6 +37,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs(SystemClock.Instance)
.As<IClock>();
services.AddSingletonAs<FeaturesService>()
.AsSelf();
services.AddSingletonAs<BackgroundUsageTracker>()
.AsSelf();

1
src/Squidex/Squidex.csproj

@ -94,6 +94,7 @@
<PackageReference Include="OrleansDashboard" Version="2.2.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="4.0.9" />
<PackageReference Include="Squidex.ClientLibrary" Version="2.8.0" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Linq" Version="4.3.0" />

3
src/Squidex/WebStartup.cs

@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
using Squidex.Areas.Api;
using Squidex.Areas.Api.Config.Swagger;
using Squidex.Areas.Api.Controllers.Contents;
using Squidex.Areas.Api.Controllers.News;
using Squidex.Areas.Frontend;
using Squidex.Areas.IdentityServer;
using Squidex.Areas.IdentityServer.Config;
@ -92,6 +93,8 @@ namespace Squidex
config.GetSection("ui"));
services.Configure<MyUsageOptions>(
config.GetSection("usage"));
services.Configure<MyNewsOptions>(
config.GetSection("news"));
var provider = services.AddAndBuildOrleans(configuration, afterServices =>
{

1
src/Squidex/app/features/apps/declarations.ts

@ -6,4 +6,5 @@
*/
export * from './pages/apps-page.component';
export * from './pages/news-dialog.component';
export * from './pages/onboarding-dialog.component';

2
src/Squidex/app/features/apps/module.ts

@ -12,6 +12,7 @@ import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import {
AppsPageComponent,
NewsDialogComponent,
OnboardingDialogComponent
} from './declarations';
@ -30,6 +31,7 @@ const routes: Routes = [
],
declarations: [
AppsPageComponent,
NewsDialogComponent,
OnboardingDialogComponent
]
})

8
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -99,6 +99,10 @@
</sqx-app-form>
</ng-container>
<ng-container *sqxModalView="onboardingModal;onRoot:true;closeAuto:false">
<sqx-onboarding-dialog (closed)="onboardingModal.hide()"></sqx-onboarding-dialog>
<ng-container *sqxModalView="onboardingDialog;onRoot:true;closeAuto:false">
<sqx-onboarding-dialog (closed)="onboardingDialog.hide()"></sqx-onboarding-dialog>
</ng-container>
<ng-container *sqxModalView="newsDialog;onRoot:true;closeAuto:false">
<sqx-news-dialog [features]="newsFeatures" (closed)="newsDialog.hide()"></sqx-news-dialog>
</ng-container>

32
src/Squidex/app/features/apps/pages/apps-page.component.ts

@ -12,7 +12,9 @@ import {
AppsState,
AuthService,
DialogModel,
ModalModel,
FeatureDto,
LocalStoreService,
NewsService,
OnboardingService
} from '@app/shared';
@ -25,11 +27,16 @@ export class AppsPageComponent implements OnInit {
public addAppDialog = new DialogModel();
public addAppTemplate = '';
public onboardingModal = new ModalModel();
public onboardingDialog = new DialogModel();
public newsFeatures: FeatureDto[];
public newsDialog = new DialogModel();
constructor(
public readonly appsState: AppsState,
public readonly authState: AuthService,
private readonly localStore: LocalStoreService,
private readonly newsService: NewsService,
private readonly onboardingService: OnboardingService
) {
}
@ -38,9 +45,24 @@ export class AppsPageComponent implements OnInit {
this.appsState.apps.pipe(
take(1))
.subscribe(apps => {
if (this.onboardingService.shouldShow('dialog') && apps.length === 0) {
this.onboardingService.disable('dialog');
this.onboardingModal.show();
if (this.onboardingService.shouldShow('dialog')) {
if (apps.length === 0) {
this.onboardingService.disable('dialog');
this.onboardingDialog.show();
}
} else {
const newsVersion = this.localStore.getInt('squidex.news.version');
this.newsService.getFeatures(newsVersion).subscribe(result => {
if (result.version !== newsVersion) {
if (result.features.length > 0) {
this.newsFeatures = result.features;
this.newsDialog.show();
}
this.localStore.setInt('squidex.news.version', result.version);
}
});
}
});
}

17
src/Squidex/app/features/apps/pages/news-dialog.component.html

@ -0,0 +1,17 @@
<sqx-modal-dialog large="true">
<ng-container title>
New Features
</ng-container>
<ng-container content>
<div class="help">
<h1>What's new?</h1>
<div *ngFor="let feature of features">
<h4>{{feature.name}}</h4>
<div [innerHTML]="feature.text | sqxHelpMarkdown"></div>
</div>
</div>
</ng-container>
</sqx-modal-dialog>

16
src/Squidex/app/features/apps/pages/news-dialog.component.scss

@ -0,0 +1,16 @@
@import '_vars';
@import '_mixins';
:host /deep/ {
img {
@include box-shadow(0, 4px, 20px, .2);
width: 80%;
margin: 0 auto;
margin-top: 10px;
display: block;
}
p {
margin-bottom: 1.5rem;
}
}

27
src/Squidex/app/features/apps/pages/news-dialog.component.ts

@ -0,0 +1,27 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FeatureDto } from '@app/shared';
@Component({
selector: 'sqx-news-dialog',
styleUrls: ['./news-dialog.component.scss'],
templateUrl: './news-dialog.component.html'
})
export class NewsDialogComponent {
@Input()
public features: FeatureDto[];
@Output()
public closed = new EventEmitter();
public close() {
this.closed.emit();
}
}

12
src/Squidex/app/framework/services/local-store.service.spec.ts

@ -85,4 +85,16 @@ describe('LocalStore', () => {
expect(localStoreService.getBoolean('not_set')).toBe(false);
});
it('should get int from local store', () => {
const localStoreService = new LocalStoreService();
localStoreService.set('key1', 'abc');
localStoreService.setInt('key2', 2);
expect(localStoreService.getInt('key1', 13)).toBe(13);
expect(localStoreService.getInt('key2', 13)).toBe(2);
expect(localStoreService.getInt('not_set', 13)).toBe(13);
});
});

12
src/Squidex/app/framework/services/local-store.service.ts

@ -34,6 +34,12 @@ export class LocalStoreService {
return value === 'true';
}
public getInt(key: string, fallback = 0): number {
const value = this.get(key);
return value ? (parseInt(value, 10) || fallback) : fallback;
}
public set(key: string, value: string) {
try {
this.store.setItem(key, value);
@ -47,4 +53,10 @@ export class LocalStoreService {
this.store.setItem(key, converted);
}
public setInt(key: string, value: number) {
const converted = `${value}`;
this.store.setItem(key, converted);
}
}

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

@ -139,6 +139,7 @@ import {
FileIconPipe,
GeolocationEditorComponent,
HelpComponent,
HelpMarkdownPipe,
HistoryComponent,
HistoryListComponent,
HistoryMessagePipe,

15
src/Squidex/app/shared/services/comments.service.ts

@ -15,8 +15,7 @@ import {
DateTime,
Model,
pretifyError,
Version,
HTTP
Version
} from '@app/framework';
export class CommentsDto extends Model {
@ -63,27 +62,25 @@ export class CommentsService {
public getComments(appName: string, commentsId: string, version: Version): Observable<CommentsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}?version=${version.value}`);
return HTTP.getVersioned(this.http, url).pipe(
return this.http.get<any>(url).pipe(
map(response => {
const body: any = response.payload.body;
return new CommentsDto(
body.createdComments.map((item: any) => {
response.createdComments.map((item: any) => {
return new CommentDto(
item.id,
DateTime.parseISO_UTC(item.time),
item.text,
item.user);
}),
body.updatedComments.map((item: any) => {
response.updatedComments.map((item: any) => {
return new CommentDto(
item.id,
DateTime.parseISO_UTC(item.time),
item.text,
item.user);
}),
body.deletedComments,
new Version(body.version)
response.deletedComments,
new Version(response.version)
);
}),
pretifyError('Failed to load comments.'));

4
src/Squidex/app/shared/services/news.service.spec.ts

@ -37,11 +37,11 @@ describe('NewsService', () => {
let features: FeaturesDto;
newsService.getFeatures().subscribe(result => {
newsService.getFeatures(13).subscribe(result => {
features = result;
});
const req = httpMock.expectOne('http://service/p/api/news/features');
const req = httpMock.expectOne('http://service/p/api/news/features?version=13');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();

4
src/Squidex/app/shared/services/news.service.ts

@ -40,8 +40,8 @@ export class NewsService {
) {
}
public getFeatures(): Observable<FeaturesDto> {
const url = this.apiUrl.buildUrl('api/news/features');
public getFeatures(version: number): Observable<FeaturesDto> {
const url = this.apiUrl.buildUrl(`api/news/features?version=${version}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {

12
src/Squidex/appsettings.json

@ -314,5 +314,17 @@
* The client secret for twitter.
*/
"clientSecret": "Pdu9wdN72T33KJRFdFy1w4urBKDRzIyuKpc0OItQC2E616DuZD"
},
"news": {
/*
* The app name where the news are stored.
*/
"appName": "squidex-website",
/*
* The credentials to the app (Readonly).
*/
"clientId": "squidex-website:default",
"clientSecret": "QGgqxd7bDHBTEkpC6fj8sbdPWgZrPrPfr3xzb3LKoec="
}
}

Loading…
Cancel
Save