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 { ToastService } from './core/services/toast.service';
|
||||
import { WebsocketService } from './core/services/websocket.service';
|
||||
import { DS } from './core/services/DS.service';
|
||||
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
||||
import { AlertComponent } from './core/directives/alert/alert.component';
|
||||
|
||||
@ -95,7 +96,7 @@ library.add(fas);
|
||||
}),
|
||||
AppRoutingModule
|
||||
],
|
||||
providers: [Title, ToastService, WebsocketService],
|
||||
providers: [Title, ToastService, WebsocketService, DS],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
@ -1,48 +1,42 @@
|
||||
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';
|
||||
|
||||
export type ModelId = number | string;
|
||||
|
||||
export abstract class BaseModel {
|
||||
abstract id: ModelId;
|
||||
id: ModelId;
|
||||
|
||||
constructor() {}
|
||||
|
||||
// Typescript does not allow static and abstract at the same time :(((
|
||||
static getCollectionString(): string {
|
||||
// convert an serialized version of the model to an instance of the class
|
||||
// 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;
|
||||
}
|
||||
|
||||
private static getCheckedCollectionString(): string {
|
||||
//TODO document this function.
|
||||
public getCheckedCollectionString(): string {
|
||||
const collectionString: string = this.getCollectionString();
|
||||
if (collectionString === INVALID_COLLECTION_STRING) {
|
||||
throw new ImproperlyConfigured(
|
||||
throw new ImproperlyConfiguredError(
|
||||
'Invalid collection string: Please override the static getCollectionString method!'
|
||||
);
|
||||
}
|
||||
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 {
|
||||
id: number;
|
||||
name: string;
|
||||
permissions: string[];
|
||||
permissions: string[]; //TODO permissions could be an own model?
|
||||
|
||||
constructor(id: number) {
|
||||
constructor(id: number, name?: string, permissions?: string[]) {
|
||||
super();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
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);
|
||||
this.name = name;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
getCollectionString(): string {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { BaseModel } from './baseModel';
|
||||
|
||||
// import { DS } from 'app/core/services/DS.service';
|
||||
|
||||
export class User extends BaseModel {
|
||||
//TODO potentially make them private and use getters and setters
|
||||
id: number;
|
||||
username: string;
|
||||
title: string;
|
||||
@ -18,29 +21,53 @@ export class User extends BaseModel {
|
||||
is_active: boolean;
|
||||
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();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
static getCollectionString(): string {
|
||||
return 'users/user';
|
||||
}
|
||||
// Make this static lookup methods typesafe
|
||||
// TODO: I'm not happy about this:
|
||||
// - this has to be done for every model
|
||||
// - this may be extendet, if there are more static functionallities for models.
|
||||
static get(id: number): User | undefined {
|
||||
return this._get<User>(id);
|
||||
}
|
||||
static getAll(): User[] {
|
||||
return this._getAll<User>();
|
||||
}
|
||||
static filter(callback): User[] {
|
||||
return this._filter<User>(callback);
|
||||
this.username = username;
|
||||
this.title = title;
|
||||
this.first_name = first_name;
|
||||
this.last_name = last_name;
|
||||
this.structure_level = structure_level;
|
||||
this.number = number;
|
||||
this.about_me = about_me;
|
||||
this.groups_id = groups_id;
|
||||
this.is_present = is_present;
|
||||
this.is_committee = is_committee;
|
||||
this.email = email;
|
||||
this.last_email_send = last_email_send;
|
||||
this.comment = comment;
|
||||
this.is_active = is_active;
|
||||
this.default_password = default_password;
|
||||
}
|
||||
|
||||
getCollectionString(): string {
|
||||
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 { tap } from 'rxjs/operators';
|
||||
|
||||
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
||||
import { BaseModel, ModelId } from 'app/core/models/baseModel';
|
||||
@ -11,80 +14,110 @@ interface DataStore {
|
||||
[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 {
|
||||
static DS: DataStore;
|
||||
private store: DataStore = {};
|
||||
|
||||
private constructor() {} // Just a static class!
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
static get(collectionString: string, id: ModelId): BaseModel | undefined {
|
||||
const collection: Collection = DS[collectionString];
|
||||
get(collectionString: string, id: ModelId): BaseModel | undefined {
|
||||
const collection: Collection = this.store[collectionString];
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
const model: BaseModel = collection[id];
|
||||
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) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(collection);
|
||||
}
|
||||
|
||||
// TODO: type for callback function
|
||||
static filter(collectionString: string, callback): BaseModel[] {
|
||||
filter(collectionString: string, callback): BaseModel[] {
|
||||
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) {
|
||||
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]) {
|
||||
DS[collectionString] = {};
|
||||
}
|
||||
DS[collectionString][model.id] = model;
|
||||
this.store[collectionString][model.id] = model;
|
||||
console.log('injected ; ', model);
|
||||
}
|
||||
|
||||
static injectMany(collectionString: string, models: BaseModel[]): void {
|
||||
injectMany(models: BaseModel[]): void {
|
||||
models.forEach(model => {
|
||||
DS.inject(collectionString, model);
|
||||
this.inject(model);
|
||||
});
|
||||
}
|
||||
|
||||
static eject(collectionString: string, id: ModelId) {
|
||||
if (DS[collectionString]) {
|
||||
delete DS[collectionString][id];
|
||||
eject(collectionString: string, id: ModelId) {
|
||||
if (this.store[collectionString]) {
|
||||
delete this.store[collectionString][id];
|
||||
}
|
||||
}
|
||||
static ejectMany(collectionString: string, ids: ModelId[]) {
|
||||
|
||||
ejectMany(collectionString: string, ids: ModelId[]) {
|
||||
ids.forEach(id => {
|
||||
DS.eject(collectionString, id);
|
||||
this.eject(collectionString, id);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO remove the any there and in BaseModel.
|
||||
static save(model: BaseModel): Observable<any> {
|
||||
save(model: BaseModel): Observable<BaseModel> {
|
||||
if (!model.id) {
|
||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
||||
}
|
||||
const collectionString: string = model.getCollectionString();
|
||||
// make http request to the server
|
||||
// if this was a success, inject the model into the DS
|
||||
return of();
|
||||
|
||||
//TODO not tested
|
||||
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.
|
||||
static delete(model: BaseModel): Observable<any> {
|
||||
delete(model: BaseModel): Observable<any> {
|
||||
if (!model.id) {
|
||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
||||
}
|
||||
const collectionString: string = model.getCollectionString();
|
||||
// make http request to the server
|
||||
// if this was a success, eject the model from the DS
|
||||
return of();
|
||||
|
||||
//TODO not tested
|
||||
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,
|
||||
password: password
|
||||
};
|
||||
return this.http.post<User>('/users/login/', user, httpOptions).pipe(
|
||||
return this.http.post<any>('/users/login/', user, httpOptions).pipe(
|
||||
tap(val => {
|
||||
localStorage.setItem('username', val.username);
|
||||
this.isLoggedIn = true;
|
||||
//Set the session cookie in local storrage.
|
||||
//TODO needs validation
|
||||
}),
|
||||
catchError(this.handleError())
|
||||
);
|
||||
|
@ -9,6 +9,12 @@ import { tap } from 'rxjs/operators';
|
||||
|
||||
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({
|
||||
selector: 'app-site',
|
||||
templateUrl: './site.component.html',
|
||||
@ -22,7 +28,8 @@ export class SiteComponent implements OnInit {
|
||||
private websocketService: WebsocketService,
|
||||
private router: Router,
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private translate: TranslateService
|
||||
private translate: TranslateService,
|
||||
private dS: DS
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -42,6 +49,7 @@ export class SiteComponent implements OnInit {
|
||||
// subscribe to the socket
|
||||
socket.subscribe(response => {
|
||||
console.log('log : ', response); // will contain all the config variables
|
||||
this.storeResponse(response);
|
||||
});
|
||||
|
||||
// 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 {
|
||||
console.log('selected langauge: ', lang);
|
||||
console.log('get Langs : ', this.translate.getLangs());
|
||||
|
@ -12,8 +12,11 @@ import { DS } from 'app/core/services/DS.service';
|
||||
styleUrls: ['./start.component.css']
|
||||
})
|
||||
export class StartComponent extends BaseComponent implements OnInit {
|
||||
constructor(titleService: Title) {
|
||||
private dS: DS;
|
||||
|
||||
constructor(titleService: Title, dS: DS) {
|
||||
super(titleService);
|
||||
this.dS = dS;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -22,19 +25,29 @@ export class StartComponent extends BaseComponent implements OnInit {
|
||||
|
||||
test() {
|
||||
// This can be a basic unit test ;)
|
||||
console.log(User.get(1));
|
||||
const user1: User = new User(1);
|
||||
user1.username = 'testuser';
|
||||
const user2: User = new User(2);
|
||||
user2.username = 'testuser2';
|
||||
// console.log(User.get(1));
|
||||
const user1: User = new User(32, 'testuser');
|
||||
const user2: User = new User(42, 'testuser 2');
|
||||
|
||||
DS.injectMany(User.getCollectionString(), [user1, user2]);
|
||||
console.log(User.getAll());
|
||||
console.log(User.filter(user => user.id === 1));
|
||||
console.log(`User1 | ID ${user1.id}, Name: ${user1.username}`);
|
||||
console.log(`User2 | ID ${user2.id}, Name: ${user2.username}`);
|
||||
|
||||
DS.eject(User.getCollectionString(), user1.id);
|
||||
console.log(User.getAll());
|
||||
console.log(User.filter(user => user.id === 1));
|
||||
console.log(User.filter(user => user.id === 2));
|
||||
this.dS.inject(user1);
|
||||
this.dS.inject(user2);
|
||||
console.log('All users = ', this.dS.getAll('users/user'));
|
||||
|
||||
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