simplify models, and datastore
- example on static functions and TS generics - exmaple on data encapsulation and "single responsibility"
This commit is contained in:
parent
8b31fa15f2
commit
2b60b4ef4f
@ -37,6 +37,7 @@ import { StartComponent } from './site/start/start.component';
|
|||||||
import { ToastComponent } from './core/directives/toast/toast.component';
|
import { ToastComponent } from './core/directives/toast/toast.component';
|
||||||
import { ToastService } from './core/services/toast.service';
|
import { ToastService } from './core/services/toast.service';
|
||||||
import { WebsocketService } from './core/services/websocket.service';
|
import { WebsocketService } from './core/services/websocket.service';
|
||||||
|
import { DS } from './core/services/DS.service';
|
||||||
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
||||||
import { AlertComponent } from './core/directives/alert/alert.component';
|
import { AlertComponent } from './core/directives/alert/alert.component';
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ library.add(fas);
|
|||||||
}),
|
}),
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
providers: [Title, ToastService, WebsocketService],
|
providers: [Title, ToastService, WebsocketService, DS],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -1,48 +1,42 @@
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { DS } from 'app/core/services/DS.service';
|
// import { DS } from 'app/core/services/DS.service';
|
||||||
|
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
||||||
|
|
||||||
const INVALID_COLLECTION_STRING = 'invalid-collection-string';
|
const INVALID_COLLECTION_STRING = 'invalid-collection-string';
|
||||||
|
|
||||||
export type ModelId = number | string;
|
export type ModelId = number | string;
|
||||||
|
|
||||||
export abstract class BaseModel {
|
export abstract class BaseModel {
|
||||||
abstract id: ModelId;
|
id: ModelId;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
// Typescript does not allow static and abstract at the same time :(((
|
// convert an serialized version of the model to an instance of the class
|
||||||
static getCollectionString(): string {
|
// jsonString is usually the server respince
|
||||||
|
// T is the target model, User, Motion, Whatever
|
||||||
|
// demands full functionening Models with constructors
|
||||||
|
static fromJSON(jsonString: {}, T): BaseModel {
|
||||||
|
// create an instance of the User class
|
||||||
|
const model = Object.create(T.prototype);
|
||||||
|
// copy all the fields from the json object
|
||||||
|
return Object.assign(model, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
//hast to be overwritten by the children.
|
||||||
|
//Could be more generic: e.g. a model-enum
|
||||||
|
public getCollectionString(): string {
|
||||||
return INVALID_COLLECTION_STRING;
|
return INVALID_COLLECTION_STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getCheckedCollectionString(): string {
|
//TODO document this function.
|
||||||
|
public getCheckedCollectionString(): string {
|
||||||
const collectionString: string = this.getCollectionString();
|
const collectionString: string = this.getCollectionString();
|
||||||
if (collectionString === INVALID_COLLECTION_STRING) {
|
if (collectionString === INVALID_COLLECTION_STRING) {
|
||||||
throw new ImproperlyConfigured(
|
throw new ImproperlyConfiguredError(
|
||||||
'Invalid collection string: Please override the static getCollectionString method!'
|
'Invalid collection string: Please override the static getCollectionString method!'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return collectionString;
|
return collectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static _get<T extends BaseModel>(id: ModelId): T | undefined {
|
|
||||||
return DS.get(this.getCheckedCollectionString(), id) as T;
|
|
||||||
}
|
|
||||||
protected static _getAll<T extends BaseModel>(): T[] {
|
|
||||||
return DS.getAll(this.getCheckedCollectionString()) as T[];
|
|
||||||
}
|
|
||||||
protected static _filter<T extends BaseModel>(callback): T[] {
|
|
||||||
return DS.filter(this.getCheckedCollectionString(), callback) as T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getCollectionString(): string;
|
|
||||||
|
|
||||||
save(): Observable<any> {
|
|
||||||
return DS.save(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(): Observable<any> {
|
|
||||||
return DS.delete(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,24 +3,13 @@ import { BaseModel } from './baseModel';
|
|||||||
export class Group extends BaseModel {
|
export class Group extends BaseModel {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
permissions: string[];
|
permissions: string[]; //TODO permissions could be an own model?
|
||||||
|
|
||||||
constructor(id: number) {
|
constructor(id: number, name?: string, permissions?: string[]) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
this.name = name;
|
||||||
|
this.permissions = permissions;
|
||||||
static getCollectionString(): string {
|
|
||||||
return 'users/group';
|
|
||||||
}
|
|
||||||
static get(id: number): Group | undefined {
|
|
||||||
return this._get<Group>(id);
|
|
||||||
}
|
|
||||||
static getAll(): Group[] {
|
|
||||||
return this._getAll<Group>();
|
|
||||||
}
|
|
||||||
static filter(callback): Group[] {
|
|
||||||
return this._filter<Group>(callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCollectionString(): string {
|
getCollectionString(): string {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { BaseModel } from './baseModel';
|
import { BaseModel } from './baseModel';
|
||||||
|
|
||||||
|
// import { DS } from 'app/core/services/DS.service';
|
||||||
|
|
||||||
export class User extends BaseModel {
|
export class User extends BaseModel {
|
||||||
|
//TODO potentially make them private and use getters and setters
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -18,29 +21,53 @@ export class User extends BaseModel {
|
|||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
default_password: string;
|
default_password: string;
|
||||||
|
|
||||||
constructor(id: number) {
|
//default constructer with every possible optinal parameter for conventient usage
|
||||||
|
constructor(
|
||||||
|
id: number,
|
||||||
|
username?: string,
|
||||||
|
title?: string,
|
||||||
|
first_name?: string,
|
||||||
|
last_name?: string,
|
||||||
|
structure_level?: string,
|
||||||
|
number?: string,
|
||||||
|
about_me?: string,
|
||||||
|
groups_id?: number[],
|
||||||
|
is_present?: boolean,
|
||||||
|
is_committee?: boolean,
|
||||||
|
email?: string,
|
||||||
|
last_email_send?: string,
|
||||||
|
comment?: string,
|
||||||
|
is_active?: boolean,
|
||||||
|
default_password?: string
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
this.username = username;
|
||||||
|
this.title = title;
|
||||||
static getCollectionString(): string {
|
this.first_name = first_name;
|
||||||
return 'users/user';
|
this.last_name = last_name;
|
||||||
}
|
this.structure_level = structure_level;
|
||||||
// Make this static lookup methods typesafe
|
this.number = number;
|
||||||
// TODO: I'm not happy about this:
|
this.about_me = about_me;
|
||||||
// - this has to be done for every model
|
this.groups_id = groups_id;
|
||||||
// - this may be extendet, if there are more static functionallities for models.
|
this.is_present = is_present;
|
||||||
static get(id: number): User | undefined {
|
this.is_committee = is_committee;
|
||||||
return this._get<User>(id);
|
this.email = email;
|
||||||
}
|
this.last_email_send = last_email_send;
|
||||||
static getAll(): User[] {
|
this.comment = comment;
|
||||||
return this._getAll<User>();
|
this.is_active = is_active;
|
||||||
}
|
this.default_password = default_password;
|
||||||
static filter(callback): User[] {
|
|
||||||
return this._filter<User>(callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCollectionString(): string {
|
getCollectionString(): string {
|
||||||
return 'users/user';
|
return 'users/user';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // convert an serialized version of the User to an instance of the class
|
||||||
|
// static fromJSON(jsonString: {}): User {
|
||||||
|
// // create an instance of the User class
|
||||||
|
// let user = Object.create(User.prototype);
|
||||||
|
// // copy all the fields from the json object
|
||||||
|
// return Object.assign(user, jsonString);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
||||||
import { BaseModel, ModelId } from 'app/core/models/baseModel';
|
import { BaseModel, ModelId } from 'app/core/models/baseModel';
|
||||||
@ -11,80 +14,110 @@ interface DataStore {
|
|||||||
[collectionString: string]: Collection;
|
[collectionString: string]: Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Todo: DRY. This is a copy from /authService. probably repository service necessary
|
||||||
|
const httpOptions = {
|
||||||
|
withCredentials: true,
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class DS {
|
export class DS {
|
||||||
static DS: DataStore;
|
private store: DataStore = {};
|
||||||
|
|
||||||
private constructor() {} // Just a static class!
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
static get(collectionString: string, id: ModelId): BaseModel | undefined {
|
get(collectionString: string, id: ModelId): BaseModel | undefined {
|
||||||
const collection: Collection = DS[collectionString];
|
const collection: Collection = this.store[collectionString];
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const model: BaseModel = collection[id];
|
const model: BaseModel = collection[id];
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
static getAll(collectionString: string): BaseModel[] {
|
|
||||||
const collection: Collection = DS[collectionString];
|
//todo return observable of base model
|
||||||
|
getAll(collectionString: string): BaseModel[] {
|
||||||
|
const collection: Collection = this.store[collectionString];
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return Object.values(collection);
|
return Object.values(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: type for callback function
|
// TODO: type for callback function
|
||||||
static filter(collectionString: string, callback): BaseModel[] {
|
filter(collectionString: string, callback): BaseModel[] {
|
||||||
return this.getAll(collectionString).filter(callback);
|
return this.getAll(collectionString).filter(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inject(collectionString: string, model: BaseModel): void {
|
inject(model: BaseModel): void {
|
||||||
|
const collectionString = model.getCollectionString();
|
||||||
|
console.log('the collection string: ', collectionString);
|
||||||
|
|
||||||
if (!model.id) {
|
if (!model.id) {
|
||||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
throw new ImproperlyConfiguredError('The model must have an id!');
|
||||||
|
} else if (collectionString === 'invalid-collection-string') {
|
||||||
|
throw new ImproperlyConfiguredError('Cannot save a BaseModel');
|
||||||
}
|
}
|
||||||
if (model.getCollectionString() !== collectionString) {
|
|
||||||
throw new ImproperlyConfiguredError('The model you try to insert has not the right collection string');
|
if (typeof this.store[collectionString] === 'undefined') {
|
||||||
|
this.store[collectionString] = {};
|
||||||
|
console.log('made new collection: ', collectionString);
|
||||||
}
|
}
|
||||||
if (!DS[collectionString]) {
|
this.store[collectionString][model.id] = model;
|
||||||
DS[collectionString] = {};
|
console.log('injected ; ', model);
|
||||||
}
|
|
||||||
DS[collectionString][model.id] = model;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static injectMany(collectionString: string, models: BaseModel[]): void {
|
injectMany(models: BaseModel[]): void {
|
||||||
models.forEach(model => {
|
models.forEach(model => {
|
||||||
DS.inject(collectionString, model);
|
this.inject(model);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static eject(collectionString: string, id: ModelId) {
|
eject(collectionString: string, id: ModelId) {
|
||||||
if (DS[collectionString]) {
|
if (this.store[collectionString]) {
|
||||||
delete DS[collectionString][id];
|
delete this.store[collectionString][id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static ejectMany(collectionString: string, ids: ModelId[]) {
|
|
||||||
|
ejectMany(collectionString: string, ids: ModelId[]) {
|
||||||
ids.forEach(id => {
|
ids.forEach(id => {
|
||||||
DS.eject(collectionString, id);
|
this.eject(collectionString, id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove the any there and in BaseModel.
|
// TODO remove the any there and in BaseModel.
|
||||||
static save(model: BaseModel): Observable<any> {
|
save(model: BaseModel): Observable<BaseModel> {
|
||||||
if (!model.id) {
|
if (!model.id) {
|
||||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
throw new ImproperlyConfiguredError('The model must have an id!');
|
||||||
}
|
}
|
||||||
const collectionString: string = model.getCollectionString();
|
const collectionString: string = model.getCollectionString();
|
||||||
// make http request to the server
|
|
||||||
// if this was a success, inject the model into the DS
|
//TODO not tested
|
||||||
return of();
|
return this.http.post<BaseModel>(collectionString + '/', model, httpOptions).pipe(
|
||||||
|
tap(response => {
|
||||||
|
console.log('the response: ', response);
|
||||||
|
this.inject(model);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove the any there and in BaseModel.
|
// TODO remove the any there and in BaseModel.
|
||||||
static delete(model: BaseModel): Observable<any> {
|
delete(model: BaseModel): Observable<any> {
|
||||||
if (!model.id) {
|
if (!model.id) {
|
||||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
throw new ImproperlyConfiguredError('The model must have an id!');
|
||||||
}
|
}
|
||||||
const collectionString: string = model.getCollectionString();
|
const collectionString: string = model.getCollectionString();
|
||||||
// make http request to the server
|
|
||||||
// if this was a success, eject the model from the DS
|
//TODO not tested
|
||||||
return of();
|
return this.http.post<BaseModel>(collectionString + '/', model, httpOptions).pipe(
|
||||||
|
tap(response => {
|
||||||
|
console.log('the response: ', response);
|
||||||
|
this.eject(collectionString, model.id);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,10 @@ export class AuthService {
|
|||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
return this.http.post<User>('/users/login/', user, httpOptions).pipe(
|
return this.http.post<any>('/users/login/', user, httpOptions).pipe(
|
||||||
tap(val => {
|
tap(val => {
|
||||||
localStorage.setItem('username', val.username);
|
localStorage.setItem('username', val.username);
|
||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
//Set the session cookie in local storrage.
|
|
||||||
//TODO needs validation
|
|
||||||
}),
|
}),
|
||||||
catchError(this.handleError())
|
catchError(this.handleError())
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,12 @@ import { tap } from 'rxjs/operators';
|
|||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core'; //showcase
|
import { TranslateService } from '@ngx-translate/core'; //showcase
|
||||||
|
|
||||||
|
//into own service
|
||||||
|
import { DS } from 'app/core/services/DS.service';
|
||||||
|
import { User } from 'app/core/models/user';
|
||||||
|
import { Group } from 'app/core/models/group';
|
||||||
|
import { BaseModel } from '../core/models/baseModel';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-site',
|
selector: 'app-site',
|
||||||
templateUrl: './site.component.html',
|
templateUrl: './site.component.html',
|
||||||
@ -22,7 +28,8 @@ export class SiteComponent implements OnInit {
|
|||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private translate: TranslateService
|
private translate: TranslateService,
|
||||||
|
private dS: DS
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -42,6 +49,7 @@ export class SiteComponent implements OnInit {
|
|||||||
// subscribe to the socket
|
// subscribe to the socket
|
||||||
socket.subscribe(response => {
|
socket.subscribe(response => {
|
||||||
console.log('log : ', response); // will contain all the config variables
|
console.log('log : ', response); // will contain all the config variables
|
||||||
|
this.storeResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
// basically everything needed for AutoUpdate
|
// basically everything needed for AutoUpdate
|
||||||
@ -55,6 +63,27 @@ export class SiteComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//test. will move to an own service later
|
||||||
|
//create models out of socket answer
|
||||||
|
storeResponse(socketResponse): void {
|
||||||
|
socketResponse.forEach(model => {
|
||||||
|
switch (model.collection) {
|
||||||
|
case 'users/group': {
|
||||||
|
this.dS.inject(BaseModel.fromJSON(model.data, Group));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'users/user': {
|
||||||
|
this.dS.inject(BaseModel.fromJSON(model.data, User));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.log('collection: "' + model.collection + '" is not yet parsed');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
selectLang(lang: string): void {
|
selectLang(lang: string): void {
|
||||||
console.log('selected langauge: ', lang);
|
console.log('selected langauge: ', lang);
|
||||||
console.log('get Langs : ', this.translate.getLangs());
|
console.log('get Langs : ', this.translate.getLangs());
|
||||||
|
@ -12,8 +12,11 @@ import { DS } from 'app/core/services/DS.service';
|
|||||||
styleUrls: ['./start.component.css']
|
styleUrls: ['./start.component.css']
|
||||||
})
|
})
|
||||||
export class StartComponent extends BaseComponent implements OnInit {
|
export class StartComponent extends BaseComponent implements OnInit {
|
||||||
constructor(titleService: Title) {
|
private dS: DS;
|
||||||
|
|
||||||
|
constructor(titleService: Title, dS: DS) {
|
||||||
super(titleService);
|
super(titleService);
|
||||||
|
this.dS = dS;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -22,19 +25,29 @@ export class StartComponent extends BaseComponent implements OnInit {
|
|||||||
|
|
||||||
test() {
|
test() {
|
||||||
// This can be a basic unit test ;)
|
// This can be a basic unit test ;)
|
||||||
console.log(User.get(1));
|
// console.log(User.get(1));
|
||||||
const user1: User = new User(1);
|
const user1: User = new User(32, 'testuser');
|
||||||
user1.username = 'testuser';
|
const user2: User = new User(42, 'testuser 2');
|
||||||
const user2: User = new User(2);
|
|
||||||
user2.username = 'testuser2';
|
|
||||||
|
|
||||||
DS.injectMany(User.getCollectionString(), [user1, user2]);
|
console.log(`User1 | ID ${user1.id}, Name: ${user1.username}`);
|
||||||
console.log(User.getAll());
|
console.log(`User2 | ID ${user2.id}, Name: ${user2.username}`);
|
||||||
console.log(User.filter(user => user.id === 1));
|
|
||||||
|
|
||||||
DS.eject(User.getCollectionString(), user1.id);
|
this.dS.inject(user1);
|
||||||
console.log(User.getAll());
|
this.dS.inject(user2);
|
||||||
console.log(User.filter(user => user.id === 1));
|
console.log('All users = ', this.dS.getAll('users/user'));
|
||||||
console.log(User.filter(user => user.id === 2));
|
|
||||||
|
console.log('try to get user with ID 1:');
|
||||||
|
const user1fromStore = this.dS.get('users/user', 1);
|
||||||
|
console.log('the user: ', user1fromStore);
|
||||||
|
|
||||||
|
console.log('inject many:');
|
||||||
|
this.dS.injectMany([user1, user2]);
|
||||||
|
|
||||||
|
console.log('eject user 1');
|
||||||
|
this.dS.eject('users/user', user1.id);
|
||||||
|
console.log(this.dS.getAll('users/user'));
|
||||||
|
|
||||||
|
// console.log(User.filter(user => user.id === 1));
|
||||||
|
// console.log(User.filter(user => user.id === 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8594
package-lock.json
generated
Normal file
8594
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user