Browse Source

User => Actor

pull/1/head
Sebastian 9 years ago
parent
commit
054ec56a51
  1. 2
      src/Squidex/Config/Domain/Serializers.cs
  2. 4
      src/Squidex/Config/Domain/WriteModule.cs
  3. 1
      src/Squidex/Config/EventStore/MyEventStoreOptions.cs
  4. 4
      src/Squidex/Config/Identity/IdentityServices.cs
  5. 3
      src/Squidex/Config/Swagger/SwaggerUsage.cs
  6. 1
      src/Squidex/Controllers/Api/Apps/AppController.cs
  7. 6
      src/Squidex/Controllers/Api/History/Models/HistoryEventDto.cs
  8. 29
      src/Squidex/Pipeline/CommandHandlers/EnrichWithActorHandler.cs
  9. 2
      src/Squidex/app/app.module.ts
  10. 39
      src/Squidex/app/features/settings/module.ts
  11. 13
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  12. 13
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  13. 6
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  14. 6
      src/Squidex/app/framework/angular/color-picker.component.spec.ts
  15. 12
      src/Squidex/app/framework/angular/date-time.pipes.spec.ts
  16. 9
      src/Squidex/app/framework/angular/date-time.pipes.ts
  17. 4
      src/Squidex/app/framework/angular/focus-on-change.directive.spec.ts
  18. 4
      src/Squidex/app/framework/angular/focus-on-init.directive.spec.ts
  19. 3
      src/Squidex/app/framework/module.ts
  20. 6
      src/Squidex/app/framework/utils/date-time.spec.ts
  21. 4
      src/Squidex/app/framework/utils/date-time.ts
  22. 19
      src/Squidex/app/shared/components/history.component.html
  23. 7
      src/Squidex/app/shared/components/history.component.scss
  24. 59
      src/Squidex/app/shared/components/history.component.ts
  25. 2
      src/Squidex/app/shared/declarations.ts
  26. 9
      src/Squidex/app/shared/module.ts
  27. 3
      src/Squidex/app/shared/services/apps.service.ts
  28. 65
      src/Squidex/app/shared/services/history.service.spec.ts
  29. 55
      src/Squidex/app/shared/services/history.service.ts
  30. 5
      src/Squidex/app/theme/_panels.scss
  31. 2
      src/Squidex/appsettings.json
  32. 4
      src/Squidex/project.json

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

@ -26,7 +26,7 @@ namespace Squidex.Config.Domain
settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.Converters.Add(new LanguageConverter()); settings.Converters.Add(new LanguageConverter());
settings.Converters.Add(new PropertiesBagConverter()); settings.Converters.Add(new PropertiesBagConverter());
settings.Converters.Add(new UserTokenConverter()); settings.Converters.Add(new RefTokenConverter());
settings.NullValueHandling = NullValueHandling.Ignore; settings.NullValueHandling = NullValueHandling.Ignore;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat; settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime; settings.DateParseHandling = DateParseHandling.DateTime;

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

@ -23,7 +23,7 @@ namespace Squidex.Config.Domain
.As<ICommandHandler>() .As<ICommandHandler>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<EnrichWithUserHandler>() builder.RegisterType<EnrichWithActorHandler>()
.As<ICommandHandler>() .As<ICommandHandler>()
.SingleInstance(); .SingleInstance();
@ -43,7 +43,7 @@ namespace Squidex.Config.Domain
.As<IEventProcessor>() .As<IEventProcessor>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<EnrichWithUserProcessor>() builder.RegisterType<EnrichWithActorProcessor>()
.As<IEventProcessor>() .As<IEventProcessor>()
.SingleInstance(); .SingleInstance();

1
src/Squidex/Config/EventStore/MyEventStoreOptions.cs

@ -5,6 +5,7 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Config.EventStore namespace Squidex.Config.EventStore
{ {
public sealed class MyEventStoreOptions public sealed class MyEventStoreOptions

4
src/Squidex/Config/Identity/IdentityServices.cs

@ -52,10 +52,10 @@ namespace Squidex.Config.Identity
{ {
options.UserInteractionOptions.ErrorUrl = "/account/error/"; options.UserInteractionOptions.ErrorUrl = "/account/error/";
}) })
.AddAspNetIdentity<IdentityUser>()
.AddInMemoryApiResources(GetApiResources()) .AddInMemoryApiResources(GetApiResources())
.AddInMemoryIdentityResources(GetIdentityResources()) .AddInMemoryIdentityResources(GetIdentityResources())
.AddSigningCredential(certificate) .AddSigningCredential(certificate);
.AddAspNetIdentity<IdentityUser>();
return services; return services;
} }

3
src/Squidex/Config/Swagger/SwaggerUsage.cs

@ -55,7 +55,8 @@ namespace Squidex.Config.Swagger
settings.TypeMappers = new List<ITypeMapper> settings.TypeMappers = new List<ITypeMapper>
{ {
new PrimitiveTypeMapper(typeof(Language), s => s.Type = JsonObjectType.String) new PrimitiveTypeMapper(typeof(Language), s => s.Type = JsonObjectType.String),
new PrimitiveTypeMapper(typeof(RefToken), s => s.Type = JsonObjectType.String)
}; };
settings.DocumentProcessors.Add(new XmlTagProcessor()); settings.DocumentProcessors.Add(new XmlTagProcessor());

1
src/Squidex/Controllers/Api/Apps/AppController.cs

@ -54,6 +54,7 @@ namespace Squidex.Controllers.Api.Apps
public async Task<IActionResult> GetApps() public async Task<IActionResult> GetApps()
{ {
var subject = HttpContext.User.OpenIdSubject(); var subject = HttpContext.User.OpenIdSubject();
var schemas = await appRepository.QueryAllAsync(subject); var schemas = await appRepository.QueryAllAsync(subject);
var response = schemas.Select(s => var response = schemas.Select(s =>

6
src/Squidex/Controllers/Api/History/Models/HistoryEventDto.cs

@ -19,6 +19,12 @@ namespace Squidex.Controllers.Api.History.Models
[Required] [Required]
public string Message { get; set; } public string Message { get; set; }
/// <summary>
/// The user who called the action.
/// </summary>
[Required]
public string Actor { get; set; }
/// <summary> /// <summary>
/// Gets a unique id for the event. /// Gets a unique id for the event.
/// </summary> /// </summary>

29
src/Squidex/Pipeline/CommandHandlers/EnrichWithUserHandler.cs → src/Squidex/Pipeline/CommandHandlers/EnrichWithActorHandler.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// EnrichWithSubjectHandler.cs // EnrichWithActorHandler.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -12,52 +12,53 @@ using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
// ReSharper disable InvertIf // ReSharper disable InvertIf
namespace Squidex.Pipeline.CommandHandlers namespace Squidex.Pipeline.CommandHandlers
{ {
public class EnrichWithUserHandler : ICommandHandler public class EnrichWithActorHandler : ICommandHandler
{ {
private readonly IHttpContextAccessor httpContextAccessor; private readonly IHttpContextAccessor httpContextAccessor;
public EnrichWithUserHandler(IHttpContextAccessor httpContextAccessor) public EnrichWithActorHandler(IHttpContextAccessor httpContextAccessor)
{ {
this.httpContextAccessor = httpContextAccessor; this.httpContextAccessor = httpContextAccessor;
} }
public Task<bool> HandleAsync(CommandContext context) public Task<bool> HandleAsync(CommandContext context)
{ {
var subjectCommand = context.Command as IUserCommand; var subjectCommand = context.Command as IActorCommand;
if (subjectCommand != null) if (subjectCommand != null)
{ {
var userToken = var actorToken =
FindUserFromSubject() ?? FindActorFromSubject() ??
FindUserFromClient(); FindActorFromClient();
if (userToken == null) if (actorToken == null)
{ {
throw new SecurityException("No user with subject or client id available"); throw new SecurityException("No actor with subject or client id available");
} }
subjectCommand.User = userToken; subjectCommand.Actor = actorToken;
} }
return Task.FromResult(false); return Task.FromResult(false);
} }
private UserToken FindUserFromSubject() private RefToken FindActorFromSubject()
{ {
var subjectId = httpContextAccessor.HttpContext.User.OpenIdSubject(); var subjectId = httpContextAccessor.HttpContext.User.OpenIdSubject();
return subjectId == null ? null : new UserToken("subject", subjectId); return subjectId == null ? null : new RefToken("subject", subjectId);
} }
private UserToken FindUserFromClient() private RefToken FindActorFromClient()
{ {
var clientId = httpContextAccessor.HttpContext.User.OpenIdClientId(); var clientId = httpContextAccessor.HttpContext.User.OpenIdClientId();
return clientId == null ? null : new UserToken("client", clientId); return clientId == null ? null : new RefToken("client", clientId);
} }
} }
} }

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

@ -22,6 +22,7 @@ import {
CurrencyConfig, CurrencyConfig,
DecimalSeparatorConfig, DecimalSeparatorConfig,
DragService, DragService,
HistoryService,
LanguageService, LanguageService,
LocalStoreService, LocalStoreService,
MustBeAuthenticatedGuard, MustBeAuthenticatedGuard,
@ -76,6 +77,7 @@ export function configCurrency() {
AppMustExistGuard, AppMustExistGuard,
AuthService, AuthService,
DragService, DragService,
HistoryService,
LanguageService, LanguageService,
LocalStoreService, LocalStoreService,
MustBeAuthenticatedGuard, MustBeAuthenticatedGuard,

39
src/Squidex/app/features/settings/module.ts

@ -8,7 +8,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from 'shared'; import {
HistoryComponent,
SqxFrameworkModule,
SqxSharedModule
} from 'shared';
import { import {
ClientComponent, ClientComponent,
@ -28,13 +32,40 @@ const routes: Routes = [
}, },
{ {
path: 'clients', path: 'clients',
component: ClientsPageComponent component: ClientsPageComponent,
children: [
{
path: 'history',
component: HistoryComponent,
data: {
channel: 'settings.clients'
}
}
]
}, { }, {
path: 'contributors', path: 'contributors',
component: ContributorsPageComponent component: ContributorsPageComponent,
children: [
{
path: 'history',
component: HistoryComponent,
data: {
channel: 'settings.contributors'
}
}
]
}, { }, {
path: 'languages', path: 'languages',
component: LanguagesPageComponent component: LanguagesPageComponent,
children: [
{
path: 'history',
component: HistoryComponent,
data: {
channel: 'settings.languages'
}
}
]
} }
] ]
} }

13
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -43,5 +43,16 @@
</form> </form>
</div> </div>
</div> </div>
<div class="panel-sidebar">
<div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-filter"></i>
</a>
</li>
</div>
</div>
</div> </div>
</div> </div>
<router-outlet></router-outlet>

13
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -63,5 +63,16 @@
</form> </form>
</div> </div>
</div> </div>
<div class="panel-sidebar">
<div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-filter"></i>
</a>
</li>
</div>
</div>
</div> </div>
</div> </div>
<router-outlet></router-outlet>

6
src/Squidex/app/features/settings/pages/languages/languages-page.component.html

@ -77,11 +77,13 @@
<div class="panel-sidebar"> <div class="panel-sidebar">
<div class="nav nav-pills nav-stacked nav-light"> <div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link"> <a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-filter"></i> <i class="icon-filter"></i>
</a> </a>
</li> </li>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<router-outlet></router-outlet>

6
src/Squidex/app/framework/angular/color-picker.component.spec.ts

@ -87,7 +87,7 @@ describe('ColorPickerComponent', () => {
colorPicker.color = selectedColor; colorPicker.color = selectedColor;
colorPicker.ngOnChanges({}); colorPicker.ngOnChanges();
expect(colorPicker.selectedColor).toBe(selectedColor); expect(colorPicker.selectedColor).toBe(selectedColor);
}); });
@ -97,7 +97,7 @@ describe('ColorPickerComponent', () => {
colorPicker.color = 'invalid'; colorPicker.color = 'invalid';
colorPicker.ngOnChanges({}); colorPicker.ngOnChanges();
expect(colorPicker.selectedColor).toBe(colorPicker.palette.defaultColor); expect(colorPicker.selectedColor).toBe(colorPicker.palette.defaultColor);
}); });
@ -108,7 +108,7 @@ describe('ColorPickerComponent', () => {
colorPicker.palette = undefined!; colorPicker.palette = undefined!;
colorPicker.color = 'invalid'; colorPicker.color = 'invalid';
colorPicker.ngOnChanges({}); colorPicker.ngOnChanges();
expect(colorPicker.selectedColor).toBe(Color.BLACK); expect(colorPicker.selectedColor).toBe(Color.BLACK);
}); });

12
src/Squidex/app/framework/angular/date-time.pipes.spec.ts

@ -11,6 +11,7 @@ import {
DayOfWeekPipe, DayOfWeekPipe,
DayPipe, DayPipe,
DurationPipe, DurationPipe,
FromNowPipe,
MonthPipe, MonthPipe,
ShortDatePipe, ShortDatePipe,
ShortTimePipe ShortTimePipe
@ -53,6 +54,17 @@ describe('MonthPipe', () => {
}); });
}); });
describe('FromNowPipe', () => {
it('should format to from now string', () => {
const pipe = new FromNowPipe();
const actual = pipe.transform(DateTime.now().addMinutes(-4), []);
const expected = '4 minutes ago';
expect(actual).toBe(expected);
});
});
describe('DayOfWeekPipe', () => { describe('DayOfWeekPipe', () => {
it('should format to short week of day string', () => { it('should format to short week of day string', () => {
const pipe = new DayOfWeekPipe(); const pipe = new DayOfWeekPipe();

9
src/Squidex/app/framework/angular/date-time.pipes.ts

@ -28,6 +28,15 @@ export class MonthPipe {
} }
} }
@Pipe({
name: 'fromNow'
})
export class FromNowPipe {
public transform(value: DateTime, args: string[]): any {
return value.toFromNow();
}
}
@Pipe({ @Pipe({
name: 'dayOfWeek' name: 'dayOfWeek'
}) })

4
src/Squidex/app/framework/angular/focus-on-change.directive.spec.ts

@ -33,7 +33,9 @@ describe('FocusOnChangeDirective', () => {
nativeElement: {} nativeElement: {}
}; };
new FocusOnChangeDirective(element, renderer as Renderer).ngOnChanges({}); const directive = new FocusOnChangeDirective(element, renderer as Renderer);
directive.select = true;
directive.ngOnChanges();
expect(calledMethods).toEqual([]); expect(calledMethods).toEqual([]);

4
src/Squidex/app/framework/angular/focus-on-init.directive.spec.ts

@ -33,7 +33,9 @@ describe('FocusOnInitDirective', () => {
nativeElement: {} nativeElement: {}
}; };
new FocusOnInitDirective(element, renderer as Renderer).ngOnInit(); const directive = new FocusOnInitDirective(element, renderer as Renderer);
directive.select = true;
directive.ngOnInit();
expect(calledMethods).toEqual([]); expect(calledMethods).toEqual([]);

3
src/Squidex/app/framework/module.ts

@ -22,6 +22,7 @@ import {
DurationPipe, DurationPipe,
FocusOnChangeDirective, FocusOnChangeDirective,
FocusOnInitDirective, FocusOnInitDirective,
FromNowPipe,
ImageDropDirective, ImageDropDirective,
ModalViewDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
@ -57,6 +58,7 @@ import {
DurationPipe, DurationPipe,
FocusOnChangeDirective, FocusOnChangeDirective,
FocusOnInitDirective, FocusOnInitDirective,
FromNowPipe,
ImageDropDirective, ImageDropDirective,
ModalViewDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
@ -83,6 +85,7 @@ import {
DurationPipe, DurationPipe,
FocusOnChangeDirective, FocusOnChangeDirective,
FocusOnInitDirective, FocusOnInitDirective,
FromNowPipe,
ImageDropDirective, ImageDropDirective,
ModalViewDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,

6
src/Squidex/app/framework/utils/date-time.spec.ts

@ -97,6 +97,12 @@ describe('DateTime', () => {
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
it('should print to from now string', () => {
const value = DateTime.now().addMinutes(-4);
expect(value.toFromNow()).toBe('4 minutes ago');
});
it('should print to valid utc string', () => { it('should print to valid utc string', () => {
const value = DateTime.parseISO_UTC('2013-10-16T12:13:14'); const value = DateTime.parseISO_UTC('2013-10-16T12:13:14');

4
src/Squidex/app/framework/utils/date-time.ts

@ -232,4 +232,8 @@ export class DateTime {
public toStringFormat(format: string): string { public toStringFormat(format: string): string {
return moment(this.value).format(format); return moment(this.value).format(format);
} }
public toFromNow(): string {
return moment(this.value).fromNow();
}
} }

19
src/Squidex/app/shared/components/history.component.html

@ -0,0 +1,19 @@
<sqx-title message="{app} | Contributors | Settings" parameter="app" value="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">
<h3 class="panel-title">History</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>
</div>
<div class="panel-main">
<div class="panel-content panel-content-blank">
<div *ngFor="let event of events">
{{event.message}}
</div>
</div>
</div>
</div>

7
src/Squidex/app/shared/components/history.component.scss

@ -0,0 +1,7 @@
@import '_vars';
@import '_mixins';
.panel {
min-width: 220px;
max-width: 1220px80px;
}

59
src/Squidex/app/shared/components/history.component.ts

@ -0,0 +1,59 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ImmutableArray, NotificationService } from 'framework';
import { AppComponentBase } from './../app-component-base';
import { AppsStoreService } from './../services/apps-store.service';
import { HistoryEventDto, HistoryService } from './../services/history.service';
import { UsersProviderService } from './../services/users-provider.service';
const FALLBACK_NAME = 'my-app';
@Component({
selector: 'sqx-history',
styleUrls: ['./history.component.scss'],
templateUrl: './history.component.html'
})
export class HistoryComponent extends AppComponentBase implements OnDestroy, OnInit {
private interval: any;
public events = ImmutableArray.empty();
constructor(appsStore: AppsStoreService, notifications: NotificationService, usersProvider: UsersProviderService,
private readonly historyService: HistoryService,
private readonly route: ActivatedRoute
) {
super(appsStore, notifications, usersProvider);
}
public ngOnDestroy() {
clearInterval(this.interval);
}
public ngOnInit() {
this.load();
this.interval =
setInterval(() => {
this.load();
}, 10000);
}
public load() {
const channel = this.route.snapshot.data['channel'];
this.appName()
.switchMap(app => this.historyService.getHistory(app, channel).retry(2))
.subscribe(dtos => {
this.events = ImmutableArray.of(dtos);
});
}
}

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

@ -7,6 +7,7 @@
export * from './components/app-form.component'; export * from './components/app-form.component';
export * from './components/dashboard-link.directive'; export * from './components/dashboard-link.directive';
export * from './components/history.component';
export * from './guards/app-must-exist.guard'; export * from './guards/app-must-exist.guard';
export * from './guards/must-be-authenticated.guard'; export * from './guards/must-be-authenticated.guard';
@ -18,6 +19,7 @@ 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/auth.service'; export * from './services/auth.service';
export * from './services/history.service';
export * from './services/languages.service'; export * from './services/languages.service';
export * from './services/users-provider.service'; export * from './services/users-provider.service';
export * from './services/users.service'; export * from './services/users.service';

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

@ -11,7 +11,8 @@ import { SqxFrameworkModule } from 'framework';
import { import {
AppFormComponent, AppFormComponent,
DashboardLinkDirective DashboardLinkDirective,
HistoryComponent
} from './declarations'; } from './declarations';
@NgModule({ @NgModule({
@ -20,11 +21,13 @@ import {
], ],
declarations: [ declarations: [
AppFormComponent, AppFormComponent,
DashboardLinkDirective DashboardLinkDirective,
HistoryComponent
], ],
exports: [ exports: [
AppFormComponent, AppFormComponent,
DashboardLinkDirective DashboardLinkDirective,
HistoryComponent
] ]
}) })
export class SqxSharedModule { } export class SqxSharedModule { }

3
src/Squidex/app/shared/services/apps.service.ts

@ -57,8 +57,7 @@ export class AppsService {
item.name, item.name,
DateTime.parseISO(item.created), DateTime.parseISO(item.created),
DateTime.parseISO(item.lastModified), DateTime.parseISO(item.lastModified),
item.permission item.permission);
);
}); });
}) })
.catch(response => handleError('Failed to load apps. Please reload.', response)); .catch(response => handleError('Failed to load apps. Please reload.', response));

65
src/Squidex/app/shared/services/history.service.spec.ts

@ -0,0 +1,65 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { Mock, Times } from 'typemoq';
import { DateTime } from 'framework';
import {
ApiUrlConfig,
AuthService,
HistoryEventDto,
HistoryService
} from './../';
describe('HistoryService', () => {
let authService: Mock<AuthService>;
let languageService: HistoryService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
languageService = new HistoryService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get history events', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/history?channel=settings.contributors'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [{
user: 'User1',
eventId: '1',
message: 'Message 1',
created: '2016-12-12T10:10'
}, {
user: 'User2',
eventId: '2',
message: 'Message 2',
created: '2016-12-13T10:10'
}]
})
)
))
.verifiable(Times.once());
let events: HistoryEventDto[] = null;
languageService.getHistory('my-app', 'settings.contributors').subscribe(result => {
events = result;
}).unsubscribe();
expect(events).toEqual(
[
new HistoryEventDto('1', 'User1', 'Message 1', DateTime.parseISO_UTC('2016-12-12T10:10')),
new HistoryEventDto('2', 'User2', 'Message 2', DateTime.parseISO_UTC('2016-12-13T10:10'))
]);
authService.verifyAll();
});
});

55
src/Squidex/app/shared/services/history.service.ts

@ -0,0 +1,55 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import {
ApiUrlConfig,
DateTime,
handleError
} from 'framework';
import { AuthService } from './auth.service';
export class HistoryEventDto {
constructor(
public readonly eventId: string,
public readonly user: string,
public readonly message: string,
public readonly created: DateTime
) {
}
}
@Injectable()
export class HistoryService {
constructor(
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
) {
}
public getHistory(appName: string, channel: string): Observable<HistoryEventDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/history?channel=${channel}`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => {
const items: any[] = response;
return items.map(item => {
return new HistoryEventDto(
item.eventId,
item.user,
item.message,
DateTime.parseISO_UTC(item.created));
});
})
.catch(response => handleError('Failed to load history. Please reload', response));
}
}

5
src/Squidex/app/theme/_panels.scss

@ -117,6 +117,11 @@ $panel-sidebar: 60px;
border-top: 1px solid $color-border; border-top: 1px solid $color-border;
} }
.panel-content-blank {
border: 0;
background: $color-card-footer;
}
.panel-sidebar { .panel-sidebar {
background: $color-card-footer; background: $color-card-footer;
border-left: 1px solid $color-border; border-left: 1px solid $color-border;

2
src/Squidex/appsettings.json

@ -10,7 +10,7 @@
"eventStore": { "eventStore": {
"ipAddress": "127.0.0.1", "ipAddress": "127.0.0.1",
"port": 1113, "port": 1113,
"prefix": "squidex_v4", "prefix": "squidex_v5",
"username": "admin", "username": "admin",
"password": "changeit" "password": "changeit"
} }

4
src/Squidex/project.json

@ -23,8 +23,8 @@
"Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"MongoDB.Driver": "2.4.0", "MongoDB.Driver": "2.4.0",
"NJsonSchema": "6.3.6185.19861", "NJsonSchema": "6.6.6192.39835",
"NSwag.AspNetCore": "8.0.0", "NSwag.AspNetCore": "8.1.0",
"OpenCover": "4.6.519", "OpenCover": "4.6.519",
"ReportGenerator": "2.5.2-beta2", "ReportGenerator": "2.5.2-beta2",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",

Loading…
Cancel
Save