autoupdate, permissions, operator, directive

-overworked login and logout
-new directive 'appOsPerms'
(former os-perms)
-appOsPerms compares with groups in Operator
-login observes operator for user-information
(also serves as example on how to user observables and subjects)
-operator observes datastore for groups
(so the  operators knows it's groups by creation or directly after an
autoupdate)
This commit is contained in:
Sean Engelhardt 2018-07-06 09:38:25 +02:00 committed by FinnStutzenstein
parent 2331ecd6b8
commit 30ac9c8e36
20 changed files with 424 additions and 155 deletions

View File

@ -4271,7 +4271,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -4292,12 +4293,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4312,17 +4315,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -4439,7 +4445,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -4451,6 +4458,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -4465,6 +4473,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -4472,12 +4481,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -4496,6 +4507,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -4576,7 +4588,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -4588,6 +4601,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -4673,7 +4687,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -4709,6 +4724,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -4728,6 +4744,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -4771,12 +4788,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},

View File

@ -1,16 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { OpenslidesService } from 'app/core/services/openslides.service';
import { AutoupdateService } from 'app/core/services/autoupdate.service';
// import { AuthService } from 'app/core/services/auth.service';
import { OperatorService } from 'app/core/services/operator.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
// export class AppComponent implements OnInit {
export class AppComponent {
constructor(
private openSlides: OpenslidesService,
private operator: OperatorService,
private autoupdate: AutoupdateService,
private translate: TranslateService
) {
@ -23,8 +25,4 @@ export class AppComponent implements OnInit {
// try to use the browser language if it is available. If not, uses english.
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
}
ngOnInit() {
this.openSlides.bootup();
}
}

View File

@ -41,6 +41,7 @@ import { AlertComponent } from './core/directives/alert/alert.component';
//translation module. TODO: Potetially a SharedModule and own files
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { PruningTranslationLoader } from './core/pruning-loader';
import { OsPermsDirective } from './core/directives/os-perms.directive';
export function HttpLoaderFactory(http: HttpClient) {
return new PruningTranslationLoader(http);
@ -60,7 +61,8 @@ library.add(fas);
SiteComponent,
StartComponent,
ProjectorContainerComponent,
AlertComponent
AlertComponent,
OsPermsDirective
],
imports: [
BrowserModule,

View File

@ -1,20 +1,16 @@
import { Injector } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { DataStoreService } from 'app/core/services/DS.service';
// import { TranslateService } from '@ngx-translate/core';
// provides functions that might be used by a lot of components
export abstract class BaseComponent {
protected injector: Injector;
protected dataStore: DataStoreService;
// would die in every scope change. disabled for now
// protected _translateService: TranslateService;
private titleSuffix = ' - OpenSlides 3';
constructor(protected titleService?: Title) {
// throws a warning even tho it is the new syntax. Ignored for now.
this.injector = Injector.create([{ provide: DataStoreService, useClass: DataStoreService, deps: [] }]);
// this._injector = Injector.create([{ provide: TranslateService, useClass: TranslateService, deps: [] }]);
}
setTitle(prefix: string) {
@ -29,11 +25,4 @@ export abstract class BaseComponent {
}
return this.dataStore;
}
// get translate(): TranslateService {
// if (this._translateService == null) {
// this._translateService = this._injector.get(TranslateService);
// }
// return this._translateService;
// }
}

View File

@ -0,0 +1,8 @@
import { OsPermsDirective } from './os-perms.directive';
describe('OsPermsDirective', () => {
it('should create an instance', () => {
const directive = new OsPermsDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,69 @@
import { Directive, Input, ElementRef, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
import { OperatorService } from 'app/core/services/operator.service';
import { BaseComponent } from 'app/base.component';
import { Group } from 'app/core/models/users/group';
@Directive({
selector: '[appOsPerms]'
})
export class OsPermsDirective extends BaseComponent {
private userPermissions: string[];
private permissions;
constructor(
private element: ElementRef,
private template: TemplateRef<any>,
private viewContainer: ViewContainerRef, //TODO private operator. OperatorService
private operator: OperatorService
) {
super();
this.userPermissions = [];
// observe groups of operator, so the directive can actively react to changes
this.operator.getObservable().subscribe(content => {
console.log('os-perms did monitor changes in observer: ', content);
if (content instanceof Group && this.permissions !== '') {
console.log('content was a Group');
this.userPermissions = [...this.userPermissions, ...content.permissions];
this.updateView();
}
});
}
@Input()
set appOsPerms(value) {
this.permissions = value;
this.readUserPermissions();
this.updateView();
}
private readUserPermissions(): void {
const opGroups = this.operator.getGroups();
console.log('operator Groups: ', opGroups);
opGroups.forEach(group => {
this.userPermissions = [...this.userPermissions, ...group.permissions];
});
}
// hides or shows a contrainer
private updateView(): void {
if (this.checkPermissions()) {
// will just render the page normally
this.viewContainer.createEmbeddedView(this.template);
} else {
// will remove the content of the container
this.viewContainer.clear();
}
}
// checks for the required permission
private checkPermissions(): boolean {
let isPermitted = false;
if (this.userPermissions && this.permissions) {
this.permissions.forEach(perm => {
isPermitted = this.userPermissions.find(userPerm => userPerm === perm) ? true : false;
});
}
return isPermitted;
}
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { ImproperlyConfiguredError } from 'app/core/exceptions';
import { BaseModel, ModelId } from 'app/core/models/baseModel';
@ -28,6 +28,8 @@ const httpOptions = {
export class DataStoreService {
// needs to be static cause becauseusing dependency injection, services are unique for a scope.
private static store: Storrage = {};
// observe datastore to enable dynamic changes in models and view
private static dataStoreSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private http: HttpClient) {}
@ -58,7 +60,7 @@ export class DataStoreService {
}
// TODO: type for callback function
// example: this.DS.filder(User, myUser => myUser.first_name === "Max")
// example: this.DS.filter(User, myUser => myUser.first_name === "Max")
filter(Type, callback): BaseModel[] {
let filterCollection = [];
const typeCollection = this.get(Type);
@ -86,7 +88,7 @@ export class DataStoreService {
DataStoreService.store[collectionString] = {};
}
DataStoreService.store[collectionString][model.id] = model;
// console.log('add model ', model, ' into Datastore');
this.setObservable(model);
});
}
@ -132,4 +134,12 @@ export class DataStoreService {
})
);
}
public getObservable(): Observable<any> {
return DataStoreService.dataStoreSubject.asObservable();
}
private setObservable(value) {
DataStoreService.dataStoreSubject.next(value);
}
}

View File

@ -2,30 +2,23 @@ import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
import { OperatorService } from './operator.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
constructor(private authService: AuthService, private operator: OperatorService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
const url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
//check if the user is already logged in.
//TODO: This is faked
if (this.authService.isLoggedIn) {
if (this.operator.id) {
return true;
}
// Store the attempted URL for redirecting
} else {
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}
}

View File

@ -3,7 +3,7 @@ import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angul
import { Observable, of, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { User } from 'app/core/models/users/user';
import { OperatorService } from 'app/core/services/operator.service';
const httpOptions = {
withCredentials: true,
@ -16,54 +16,27 @@ const httpOptions = {
providedIn: 'root'
})
export class AuthService {
isLoggedIn: boolean;
// store the URL so we can redirect after logging in
redirectUrl: string;
constructor(private http: HttpClient) {
//check for the cookie in local storrage
//TODO checks for username now since django does not seem to return a cookie
if (localStorage.getItem('username')) {
this.isLoggedIn = true;
} else {
this.isLoggedIn = false;
}
}
// Initialize the service by querying the server
// Not sure if this makes sense, since a service is not supposed to init()
init(): Observable<User | any> {
return this.http.get<User>('/users/whoami/', httpOptions).pipe(
tap(val => {
console.log('auth-init-whami : ', val);
}),
catchError(this.handleError())
);
}
constructor(private http: HttpClient, private operator: OperatorService) {}
//loggins a users. expects a user model
login(username: string, password: string): Observable<User | any> {
login(username: string, password: string): Observable<any> {
const user: any = {
username: username,
password: password
};
return this.http.post<any>('/users/login/', user, httpOptions).pipe(
tap(val => {
localStorage.setItem('username', val.username);
this.isLoggedIn = true;
}),
tap(resp => this.operator.storeUser(resp.user)),
catchError(this.handleError())
);
}
//logout the user
//TODO not yet used
logout(): Observable<User | any> {
this.isLoggedIn = false;
localStorage.removeItem('username');
return this.http.post<User>('/users/logout/', {}, httpOptions);
logout(): Observable<any> {
this.operator.clear();
return this.http.post<any>('/users/logout/', {}, httpOptions);
}
//very generic error handling function.

View File

@ -34,6 +34,11 @@ export class AutoupdateService extends BaseComponent {
constructor(private websocketService: WebsocketService) {
super();
}
// initialte autpupdate Service
startAutoupdate(): void {
console.log('start autoupdate');
this.socket = this.websocketService.connect();
this.socket.subscribe(response => {
this.storeResponse(response);

View File

@ -3,22 +3,22 @@ import { Router } from '@angular/router';
import { AuthService } from './auth.service';
//it seems that this service is useless
@Injectable({
providedIn: 'root'
})
export class OpenslidesService {
constructor(private auth: AuthService, private router: Router) {}
bootup() {
// TODO Lock the interface..
this.auth.init().subscribe(whoami => {
console.log(whoami);
if (!whoami.user && !whoami.guest_enabled) {
this.router.navigate(['/login']);
} else {
// It's ok!
}
});
}
// now in authService
// bootup() {
// // TODO Lock the interface..
// this.auth.init().subscribe(whoami => {
// console.log(whoami);
// if (!whoami.user && !whoami.guest_enabled) {
// this.router.navigate(['/login']);
// } else {
// // It's ok!
// }
// });
// }
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { OperatorService } from './operator.service';
describe('OperatorService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [OperatorService]
});
});
it('should be created', inject([OperatorService], (service: OperatorService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,198 @@
import { Injectable } from '@angular/core';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { tap, catchError, share } from 'rxjs/operators';
import { BaseComponent } from 'app/base.component';
import { Group } from 'app/core/models/users/group';
// TODO: Dry
const httpOptions = {
withCredentials: true,
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
@Injectable({
providedIn: 'root'
})
export class OperatorService extends BaseComponent {
// default variables
about_me: string;
comment: string;
default_password: string;
email: string;
first_name: string;
groups_id: number[];
id: number;
is_active: boolean;
is_committee: boolean;
is_present: boolean;
last_email_send: string;
last_name: string;
number: string;
structure_level: string;
title: string;
username: string;
logged_in: boolean;
// subject
private operatorSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
// real groups, once they arrived in datastore
private groups: Group[] = new Array();
constructor(private http: HttpClient) {
super();
// recreate old operator from localStorage.
if (localStorage.getItem('operator')) {
const oldOperator = JSON.parse(localStorage.getItem('operator'));
if (Object.keys(oldOperator).length > 0) {
this.storeUser(oldOperator);
}
}
// observe the datastore now to avoid race conditions. Ensures to
// find the groups in time
this.observeDataStore();
}
// calls 'whoami' to find out the operator
public whoAmI(): Observable<any> {
return this.http.get<any>('/users/whoami/', httpOptions).pipe(
tap(whoami => {
if (whoami && whoami.user) {
this.storeUser(whoami.user);
}
}),
catchError(this.handleError())
);
}
public storeUser(user: any): void {
// store in file
this.about_me = user.about_me;
this.comment = user.comment;
this.default_password = user.default_password;
this.email = user.email;
this.first_name = user.first_name;
this.groups_id = user.groups_id;
this.id = user.id;
this.is_active = user.is_active;
this.is_committee = user.is_committee;
this.is_present = user.is_present;
this.last_email_send = user.last_email_send;
this.last_name = user.last_name;
this.number = user.number;
this.structure_level = user.structure_level;
this.title = user.title;
this.username = user.username;
// also store in localstorrage
this.updateLocalStorrage();
// update mode to inform observers
this.updateMode();
}
public clear() {
this.about_me = null;
this.comment = null;
this.default_password = null;
this.email = null;
this.first_name = null;
this.groups_id = null;
this.id = null;
this.is_active = null;
this.is_committee = null;
this.is_present = null;
this.last_email_send = null;
this.last_name = null;
this.number = null;
this.structure_level = null;
this.title = null;
this.username = null;
this.updateMode();
localStorage.removeItem('operator');
}
private updateLocalStorrage(): void {
localStorage.setItem('operator', JSON.stringify(this.getUpdateObject()));
console.log('update local storrage: groups: ', this.groups_id);
}
private updateMode(): void {
this.setObservable(this.getUpdateObject());
}
private getUpdateObject(): any {
return {
about_me: this.about_me,
comment: this.comment,
default_password: this.default_password,
email: this.email,
first_name: this.first_name,
groups_id: this.groups_id,
id: this.id,
is_active: this.is_active,
is_committee: this.is_committee,
is_present: this.is_present,
last_email_send: this.last_email_send,
last_name: this.last_name,
number: this.number,
structure_level: this.structure_level,
title: this.title,
username: this.username,
logged_in: this.logged_in
};
}
// observe dataStore to set groups once they are there
// TODO logic to remove groups / user from certain groups
private observeDataStore(): void {
console.log('Operator observes DataStore');
this.DS.getObservable().subscribe(newModel => {
if (newModel instanceof Group) {
this.addGroup(newModel);
}
});
}
// read out the Groups from the DataStore by the operators 'groups_id'
// requires that the DataStore has been setup (websocket.service)
// requires that the whoAmI did return a valid operator
public readGroupsFromStore(): void {
this.DS.filter(Group, myGroup => {
if (this.groups_id.includes(myGroup.id)) {
this.addGroup(myGroup);
}
});
}
public getObservable() {
return this.operatorSubject.asObservable();
}
private setObservable(value) {
this.operatorSubject.next(value);
}
public getGroups() {
return this.groups;
}
// if the operator has the corresponding ID, set the group
private addGroup(newGroup: Group): void {
if (this.groups_id.includes(newGroup.id)) {
this.groups.push(newGroup);
// inform the observers about new groups (appOsPerms)
console.log('pushed a group into operator');
this.setObservable(newGroup);
}
}
// TODO Dry
private handleError<T>() {
return (error: any): Observable<T> => {
console.error(error);
return of(error);
};
}
}

View File

@ -16,4 +16,12 @@
<div class="app-content">
Agenda Works
<br/>
<div>
everyone should see this
</div>
<br/>
<div *appOsPerms="['agenda.can_see']">
Only permitted users should see this
</div>
</div>

View File

@ -5,6 +5,7 @@ import { MatSnackBar } from '@angular/material';
import { BaseComponent } from 'app/base.component';
import { AuthService } from 'app/core/services/auth.service';
import { OperatorService } from 'app/core/services/operator.service';
@Component({
selector: 'app-login',
@ -19,20 +20,22 @@ export class LoginComponent extends BaseComponent implements OnInit {
constructor(
titleService: Title,
private authService: AuthService,
private operator: OperatorService,
private router: Router,
private snackBar: MatSnackBar
) {
super(titleService);
this.setInfo();
}
ngOnInit() {
//TODO translate
super.setTitle('Anmelden');
}
super.setTitle('Log In');
setInfo() {
this.info = 'Logged in? ' + (this.authService.isLoggedIn ? 'in' : 'out');
// if there is stored login information, try to login directly.
this.operator.getObservable().subscribe(user => {
if (user && user.id) {
this.router.navigate(['/']);
}
});
}
openSnackBar(message: string) {
@ -42,23 +45,16 @@ export class LoginComponent extends BaseComponent implements OnInit {
}
// Todo: This serves as a prototype and need enhancement,
//like saving a "logged in state" and real checking the server
// if logIn was fine
// like saving a "logged in state" and real checking the server
formLogin(): void {
this.authService.login(this.username, this.password).subscribe(res => {
if (res.status === 400) {
//TODO translate
console.log('res: ', res);
this.openSnackBar(res.error.detail);
} else {
// this.toastService.success('Logged in! :)');
this.setInfo();
if (this.authService.isLoggedIn) {
// Get the redirect URL from our auth service
// If no redirect has been set, use the default
if (res.user_id) {
const redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/';
// Redirect the user
this.router.navigate([redirect]);
}
}

View File

@ -11,8 +11,8 @@
<!-- User Menu -->
<mat-expansion-panel>
<mat-expansion-panel-header>
<!-- TODO use an operator service or get from whoami -->
UserName
<!-- Get the username from operator -->
{{username}}
</mat-expansion-panel-header>
<mat-action-row>
<button (click)='logOutButton()' mat-button>Logout</button>

View File

@ -3,6 +3,8 @@ import { Router } from '@angular/router';
import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
import { AuthService } from 'app/core/services/auth.service';
import { AutoupdateService } from 'app/core/services/autoupdate.service';
import { OperatorService } from 'app/core/services/operator.service';
import { Subject } from 'rxjs';
import { tap } from 'rxjs/operators';
@ -15,10 +17,13 @@ import { BaseComponent } from 'app/base.component';
styleUrls: ['./site.component.css']
})
export class SiteComponent extends BaseComponent implements OnInit {
username = this.operator.username;
isMobile = false;
constructor(
private authService: AuthService,
private autoupdateService: AutoupdateService,
private operator: OperatorService,
private router: Router,
private breakpointObserver: BreakpointObserver,
private translate: TranslateService
@ -39,7 +44,18 @@ export class SiteComponent extends BaseComponent implements OnInit {
//get a translation via code: use the translation service
this.translate.get('Motions').subscribe((res: string) => {
console.log(res);
console.log('translation of motions in the target language: ' + res);
});
//start autoupdate if the user is logged in:
this.operator.whoAmI().subscribe(resp => {
if (resp.user) {
this.autoupdateService.startAutoupdate();
} else {
//if whoami is not sucsessfull, forward to login again
this.operator.clear();
this.router.navigate(['/login']);
}
});
}

View File

@ -5,7 +5,8 @@
<div class="app-content" translate>
<span>{{'Welcome to OpenSlides' | translate}}</span>
<br/>
<p translate [translateParams]="{user: 'Tim'}">Hello user</p>
<!-- <p translate [translateParams]="{user: 'Tim'}">Hello user</p> -->
<p>{{'Hello user' | translate:username}}</p>
<button type="button" (click)="DataStoreTest()">DataStoreTest</button>
<br/>
<button type="button" (click)="TranslateTest()">Translate in console</button>

View File

@ -5,6 +5,7 @@ import { BaseComponent } from 'app/base.component';
import { TranslateService } from '@ngx-translate/core'; //showcase
// for testing the DS and BaseModel
import { OperatorService } from 'app/core/services/operator.service';
import { User } from 'app/core/models/users/user';
import { Group } from 'app/core/models/users/group';
@ -14,12 +15,15 @@ import { Group } from 'app/core/models/users/group';
styleUrls: ['./start.component.css']
})
export class StartComponent extends BaseComponent implements OnInit {
constructor(titleService: Title, private translate: TranslateService) {
//useage of translation with variables in code and view
username = { user: this.operator.username };
constructor(titleService: Title, private translate: TranslateService, private operator: OperatorService) {
super(titleService);
}
ngOnInit() {
super.setTitle('Start page');
super.setTitle('Start page'); //TODO translate
}
//quick testing of some data store functions

View File

@ -1,35 +0,0 @@
{
"name": "openslides",
"private": true,
"scripts": {
"prepublish": "bower install && gulp",
"karma": "karma start tests/karma/karma.conf.js",
"karma:watch": "karma start tests/karma/karma.conf.js --single-run=false"
},
"devDependencies": {
"angular-mocks": "~1.5.11",
"bower": "^1.8.0",
"gulp": "~4.0.0",
"gulp-angular-gettext": "^2.2.0",
"gulp-angular-templatecache": "^2.0.0",
"gulp-concat": "^2.6.1",
"gulp-cssnano": "^2.1.2",
"gulp-if": "^2.0.2",
"gulp-inject-string": "^1.1.0",
"gulp-jshint": "^2.0.4",
"gulp-rename": "^1.2.2",
"gulp-sass": "^3.1.0",
"gulp-sourcemaps": "^2.6.0",
"gulp-uglify": "^2.1.2",
"jasmine-core": "^2.6.1",
"jshint": "^2.9.4",
"karma": "^1.5.0",
"karma-chrome-launcher": "^2.0.0",
"karma-jasmine": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.4",
"main-bower-files": "^2.13.1",
"sprintf-js": "^1.0.3",
"through2": "^2.0.3",
"yargs": "^7.1.0"
}
}