From f69886512c16259b29a20f13d3f60e5a5a917bea Mon Sep 17 00:00:00 2001 From: christophercr Date: Fri, 12 Oct 2018 09:53:13 +0200 Subject: [PATCH] feat(stark-demo): integrate "angular-in-memory-web-api" to mock backend (needed for GitHub pages) --- showcase/config/json-server/data.json | 17 +- showcase/karma.conf.ci.js | 5 + showcase/karma.conf.js | 20 +- showcase/package-lock.json | 21 +- showcase/package.json | 1 + showcase/src/app/app.module.ts | 13 +- showcase/src/app/home/home.component.html | 21 +- showcase/src/app/home/home.component.ts | 20 +- .../in-memory-data/in-memory-data.module.ts | 24 +++ showcase/src/app/in-memory-data/index.ts | 3 + .../in-memory-data.interceptor.ts | 199 ++++++++++++++++++ .../app/in-memory-data/interceptors/index.ts | 1 + .../services/in-memory-data.service.ts | 123 +++++++++++ .../src/app/in-memory-data/services/index.ts | 1 + showcase/src/stark-app-config.json | 1 - starter/package-lock.json | 16 +- 16 files changed, 433 insertions(+), 53 deletions(-) create mode 100644 showcase/src/app/in-memory-data/in-memory-data.module.ts create mode 100644 showcase/src/app/in-memory-data/index.ts create mode 100644 showcase/src/app/in-memory-data/interceptors/in-memory-data.interceptor.ts create mode 100644 showcase/src/app/in-memory-data/interceptors/index.ts create mode 100644 showcase/src/app/in-memory-data/services/in-memory-data.service.ts create mode 100644 showcase/src/app/in-memory-data/services/index.ts diff --git a/showcase/config/json-server/data.json b/showcase/config/json-server/data.json index c5af73a269..449e4f5b59 100644 --- a/showcase/config/json-server/data.json +++ b/showcase/config/json-server/data.json @@ -3,5 +3,20 @@ { "uuid": "1", "username": "janedoe", "firstName": "Jane", "lastName": "Doe", "language": "en", "roles": ["admin"] }, { "uuid": "2", "username": "johndoe", "firstName": "John", "lastName": "Doe", "language": "fr", "roles": ["manager"] }, { "uuid": "3", "username": "chucknorris", "firstName": "Chuck", "lastName": "Norris", "language": "nl", "roles": ["developer"] } - ] + ], + "userprofile": [ + { + "uuid": "4cc28d14-16d3-46e7-a05a-487533c4cc26", + "username": "johndoe", + "roles": ["manager"], + "details": { + "firstName": "John", + "lastName": "Doe", + "language": "en", + "referenceNumber": "54321", + "mail": "john.doe@nbb.be" + } + } + ], + "keepalive": [] } diff --git a/showcase/karma.conf.ci.js b/showcase/karma.conf.ci.js index e3356dbbc7..7b4bd85e5f 100644 --- a/showcase/karma.conf.ci.js +++ b/showcase/karma.conf.ci.js @@ -2,10 +2,15 @@ * Load karma config from Stark */ const defaultKarmaCIConfig = require("./node_modules/@nationalbankbelgium/stark-testing/karma.conf.ci.js").rawKarmaConfig; +const karmaTypescriptBundlerAliasResolution = require("./karma.conf").karmaTypescriptBundlerAliasResolution; const karmaTypescriptExclusions = require("./karma.conf").karmaTypescriptExclusions; // start customizing the KarmaCI configuration from stark-testing const starkShowcaseSpecificConfiguration = Object.assign({}, defaultKarmaCIConfig, { + // change the module resolution for the KarmaTypescript bundler + karmaTypescriptConfig: Object.assign(defaultKarmaCIConfig.karmaTypescriptConfig, { + bundlerOptions: Object.assign(defaultKarmaCIConfig.karmaTypescriptConfig.bundlerOptions, karmaTypescriptBundlerAliasResolution) + }), exclude: [...defaultKarmaCIConfig.exclude, ...karmaTypescriptExclusions] }); diff --git a/showcase/karma.conf.js b/showcase/karma.conf.js index 67aabf76fe..f3bd66c10f 100644 --- a/showcase/karma.conf.js +++ b/showcase/karma.conf.js @@ -8,11 +8,28 @@ const helpers = require("./node_modules/@nationalbankbelgium/stark-testing/helpe */ const defaultKarmaConfig = require("./node_modules/@nationalbankbelgium/stark-testing/karma.conf.js").rawKarmaConfig; -// entry files of the "@nationalbankbelgium/stark-ui" module imported in mock files +// exclude all code example files imported in the demo pages const karmaTypescriptExclusions = [...defaultKarmaConfig.exclude, "src/assets/examples/**"]; +const karmaTypescriptBundlerAliasResolution = { + resolve: { + alias: { + // adapt the resolution of "angular-in-memory-web-api" modules because we don't want to add "@angular/http" to the Showcase npm dependencies! + // see https://github.com/angular/in-memory-web-api/issues/215 + "angular-in-memory-web-api/http-client-in-memory-web-api.module": + "./node_modules/angular-in-memory-web-api/bundles/in-memory-web-api.umd.js", + "angular-in-memory-web-api/interfaces": "./node_modules/angular-in-memory-web-api/bundles/in-memory-web-api.umd.js", + "@angular/http": "./node_modules/@angular/common/bundles/common-http.umd.js" + } + } +}; + // start customizing the KarmaCI configuration from stark-testing const starkShowcaseSpecificConfiguration = Object.assign({}, defaultKarmaConfig, { + // change the module resolution for the KarmaTypescript bundler + karmaTypescriptConfig: Object.assign(defaultKarmaConfig.karmaTypescriptConfig, { + bundlerOptions: Object.assign(defaultKarmaConfig.karmaTypescriptConfig.bundlerOptions, karmaTypescriptBundlerAliasResolution) + }), // list of files to exclude exclude: karmaTypescriptExclusions }); @@ -22,5 +39,6 @@ module.exports = { default: function(config) { return config.set(starkShowcaseSpecificConfiguration); }, + karmaTypescriptBundlerAliasResolution: karmaTypescriptBundlerAliasResolution, karmaTypescriptExclusions: karmaTypescriptExclusions }; diff --git a/showcase/package-lock.json b/showcase/package-lock.json index 7bf5f3636e..790261ebb1 100644 --- a/showcase/package-lock.json +++ b/showcase/package-lock.json @@ -961,7 +961,7 @@ "@types/node": "8.10.15", "coveralls": "3.0.2", "istanbul-lib-instrument": "2.3.2", - "jasmine-core": "3.2.1", + "jasmine-core": "3.3.0", "karma": "3.1.1", "karma-chrome-launcher": "2.2.0", "karma-coverage": "1.1.2", @@ -1148,7 +1148,7 @@ "@types/nouislider": "9.0.4", "@types/prismjs": "1.9.0", "normalize.css": "8.0.0", - "nouislider": "12.0.0", + "nouislider": "12.1.0", "prettier": "1.14.3", "pretty-data": "0.40.0", "prismjs": "1.15.0" @@ -1703,6 +1703,11 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular-in-memory-web-api": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.6.1.tgz", + "integrity": "sha512-6Fls8XE3UC8NoydpcC82hY3KSRU4dXbGFjs3w+XLReL+Ry8zLeBHb/CZ9F+Kbg1yBH+eKamzd0vLr0+df+yC5g==" + }, "angular-named-lazy-chunks-webpack-plugin": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/angular-named-lazy-chunks-webpack-plugin/-/angular-named-lazy-chunks-webpack-plugin-2.0.0.tgz", @@ -7632,9 +7637,9 @@ } }, "jasmine-core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.2.1.tgz", - "integrity": "sha512-pa9tbBWgU0EE4SWgc85T4sa886ufuQdsgruQANhECYjwqgV4z7Vw/499aCaP8ZH79JDS4vhm8doDG9HO4+e4sA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.3.0.tgz", + "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==", "dev": true }, "jasmine-diff": { @@ -9599,9 +9604,9 @@ "integrity": "sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A==" }, "nouislider": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-12.0.0.tgz", - "integrity": "sha512-wUr7AJwDQ2oraHgNjLnELjvghTJrKskELowAhdyk01zN6jpXegoyVQJL2Tzt/hp9qwnx+E5KwWS4RNbIMizVcw==" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-12.1.0.tgz", + "integrity": "sha512-SAOabF6hBm8201c6LDbkVOVhgwY49+/ms72ZLUF2qkN5RCf7FfUvEh/hGZ7XcwZHU+I/grlicPmcSk1/rrMnOw==" }, "npm-package-arg": { "version": "6.1.0", diff --git a/showcase/package.json b/showcase/package.json index 9754bfb3a2..77f85d0b3b 100644 --- a/showcase/package.json +++ b/showcase/package.json @@ -127,6 +127,7 @@ "@nationalbankbelgium/stark-core": "file:../dist/packages-dist/stark-core/nationalbankbelgium-stark-core-10.0.0-beta.0-8d56253.tgz", "@nationalbankbelgium/stark-ui": "file:../dist/packages-dist/stark-ui/nationalbankbelgium-stark-ui-10.0.0-beta.0-8d56253.tgz", "@uirouter/visualizer": "6.0.2", + "angular-in-memory-web-api": "0.6.1", "basscss": "8.0.10", "core-js": "2.5.7", "eligrey-classlist-js-polyfill": "1.2.20180112", diff --git a/showcase/src/app/app.module.ts b/showcase/src/app/app.module.ts index cb505d3ca8..320369aa1d 100644 --- a/showcase/src/app/app.module.ts +++ b/showcase/src/app/app.module.ts @@ -20,7 +20,8 @@ import { MatListModule } from "@angular/material/list"; import { MatSidenavModule } from "@angular/material/sidenav"; import { MatTooltipModule } from "@angular/material/tooltip"; import { DateAdapter } from "@angular/material/core"; -import { SharedModule } from "./shared"; +import { SharedModule } from "./shared/shared.module"; +import { InMemoryDataModule } from "./in-memory-data/in-memory-data.module"; import { Observable, of } from "rxjs"; import { filter, map } from "rxjs/operators"; @@ -39,6 +40,7 @@ import { StarkLoggingActionTypes, StarkLoggingModule, StarkMockData, + starkPreloadingStateName, StarkRoutingModule, StarkSessionModule, StarkSessionService, @@ -206,10 +208,12 @@ export const metaReducers: MetaReducer[] = ENV !== "production" ? [logger }), TranslateModule.forRoot(), NgIdleModule.forRoot(), - NgIdleKeepaliveModule.forRoot(), // FIXME: disabled in stark-app-config.json for now until json-server is integrated + NgIdleKeepaliveModule.forRoot(), StarkHttpModule.forRoot(), StarkLoggingModule.forRoot(), - StarkSessionModule.forRoot(), + StarkSessionModule.forRoot({ + loginStateName: starkPreloadingStateName // get rid of the Login page in the Showcase :-) + }), StarkErrorHandlingModule.forRoot(), StarkSettingsModule.forRoot(), StarkRoutingModule.forRoot(), @@ -237,7 +241,8 @@ export const metaReducers: MetaReducer[] = ENV !== "production" ? [logger position: "top right", actionClasses: [] }), - StarkSessionUiModule.forRoot() + StarkSessionUiModule.forRoot(), + InMemoryDataModule ], /** * Expose our Services and Providers into Angular's dependency injection. diff --git a/showcase/src/app/home/home.component.html b/showcase/src/app/home/home.component.html index b7d0f1d94a..f2df0c373b 100644 --- a/showcase/src/app/home/home.component.html +++ b/showcase/src/app/home/home.component.html @@ -1,5 +1,5 @@
-

+

@@ -28,17 +28,14 @@

-

Stark Core

-
-
-
-

Stark UI

-
-
-
-

Showcase

-
-
+

Stark Core

+
+
+

Stark UI

+
+
+

Showcase

+
diff --git a/showcase/src/app/home/home.component.ts b/showcase/src/app/home/home.component.ts index ce97fdb30f..235223d1c0 100644 --- a/showcase/src/app/home/home.component.ts +++ b/showcase/src/app/home/home.component.ts @@ -1,8 +1,8 @@ import { Component, Inject, OnInit } from "@angular/core"; -import { STARK_LOGGING_SERVICE, StarkErrorImpl, StarkLoggingService } from "@nationalbankbelgium/stark-core"; +import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; @Component({ - selector: "home", // + selector: "home", templateUrl: "./home.component.html" }) export class HomeComponent implements OnInit { @@ -11,20 +11,4 @@ export class HomeComponent implements OnInit { public ngOnInit(): void { this.loggingService.debug("hello from `Home` component"); } - - public logError(): void { - try { - throw new Error("Invoked error"); - } catch (error) { - this.loggingService.error("Logging the Error", error); - } - } - - public logStarkError(): void { - try { - throw new Error("Invoked error"); - } catch (error) { - this.loggingService.error("Logging the StarkError", new StarkErrorImpl(error)); - } - } } diff --git a/showcase/src/app/in-memory-data/in-memory-data.module.ts b/showcase/src/app/in-memory-data/in-memory-data.module.ts new file mode 100644 index 0000000000..a38fd5a3bd --- /dev/null +++ b/showcase/src/app/in-memory-data/in-memory-data.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from "@angular/core"; +import { HTTP_INTERCEPTORS } from "@angular/common/http"; +// using the full path to import the "HttpClientInMemoryWebApiModule" to avoid adding "@angular/http" to Showcase npm dependencies! +// see https://github.com/angular/in-memory-web-api/issues/215 +import { HttpClientInMemoryWebApiModule } from "angular-in-memory-web-api/http-client-in-memory-web-api.module"; +import { InMemoryDataService } from "./services"; +import { InMemoryDataHttpInterceptor } from "./interceptors"; + +@NgModule({ + imports: [ + // advanced configuration for the HttpClientInMemoryWebApiModule (see https://github.com/angular/in-memory-web-api#advanced-features) + // see all possible options in InMemoryBackendConfigArgs in node_modules/angular-in-memory-web-api/interfaces.d.ts + HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, { + delay: 100, + apiBase: "/", + passThruUnknownUrl: true + }) + ], + providers: [ + // Add the InMemoryDataHttpInterceptor as an Http interceptor to adapt requests/responses according to the NBB Rest API Guide + { provide: HTTP_INTERCEPTORS, useClass: InMemoryDataHttpInterceptor, multi: true } + ] +}) +export class InMemoryDataModule {} diff --git a/showcase/src/app/in-memory-data/index.ts b/showcase/src/app/in-memory-data/index.ts new file mode 100644 index 0000000000..ad125681b5 --- /dev/null +++ b/showcase/src/app/in-memory-data/index.ts @@ -0,0 +1,3 @@ +export * from "./interceptors"; +export * from "./services"; +export * from "./in-memory-data.module"; diff --git a/showcase/src/app/in-memory-data/interceptors/in-memory-data.interceptor.ts b/showcase/src/app/in-memory-data/interceptors/in-memory-data.interceptor.ts new file mode 100644 index 0000000000..28c7808f2a --- /dev/null +++ b/showcase/src/app/in-memory-data/interceptors/in-memory-data.interceptor.ts @@ -0,0 +1,199 @@ +import { Injectable } from "@angular/core"; +import { HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; + +const _cloneDeep: Function = require("lodash/cloneDeep"); +const _uniqueId: Function = require("lodash/uniqueId"); + +export interface HostUrlParts { + protocol?: string; + domain: string; + port?: string; + path: string; +} + +/** + * Angular Http interceptor that normalizes the requests and responses sent to/received from the "angular-in-memory-web-api" database + * + * Defined in the InMemoryDataModule set in showcase/src/app/in-memory-data/in-memory-data.module.ts + */ +@Injectable() +export class InMemoryDataHttpInterceptor implements HttpInterceptor { + protected xsrfCookieName: string = "XSRF-TOKEN"; + + public constructor() { + /* empty constructor */ + } + + /** + * Intercept an outgoing `HttpRequest` and optionally transform it if necessary. + * @param request - The intercepted outgoing `HttpRequest` + * @param next - The next request handler where the `HttpRequest` will be forwarded to + * @returns The modified `HttpRequest` with the XSRF configuration enabled. + */ + public intercept(request: HttpRequest, next: HttpHandler): Observable> { + let httpRequest: HttpRequest = request; + const isGetCollectionRequest: boolean = this.isGetCollectionRequest(httpRequest); + + if (isGetCollectionRequest) { + httpRequest = httpRequest.clone({ + params: httpRequest.params.delete("mockCollectionRequest") + }); + } + + if (httpRequest.method === "POST" || httpRequest.method === "PUT") { + httpRequest = this.interceptRequestPOST(httpRequest); + } + + if (httpRequest.method === "GET") { + httpRequest = this.interceptRequestGET(httpRequest); + } + + return next + .handle(httpRequest) // pass request through to the next request handler + .pipe( + map((httpResponse: HttpEvent) => { + if (httpResponse instanceof HttpResponse) { + if (httpRequest.method === "GET") { + httpResponse = this.interceptResponseGET(httpRequest, httpResponse); + } + + if (isGetCollectionRequest) { + httpResponse = this.normalizeCollectionResponse(httpResponse); + } + + this.setXSRFCookie(); + } + + return httpResponse; + }) + ); + } + + protected interceptRequestGET(request: HttpRequest): HttpRequest { + let modifiedRequest: HttpRequest = request; + + // for the "userprofile" request, remove the "style" param to let the in-memory-db handle the request properly + if (request.url.match(/\/security\/userprofile/)) { + modifiedRequest = request.clone({ + url: "/userprofile", + params: request.params.delete("style") + }); + } + + return modifiedRequest; + } + + protected interceptRequestPOST(request: HttpRequest): HttpRequest { + let modifiedRequest: HttpRequest; + + // add a unique Id to the new item(s) if they don't have any + const normalizedBody: any = _cloneDeep(request.body); + this.deepSetUniqueId(normalizedBody, "id"); + + modifiedRequest = request.clone({ + body: normalizedBody + }); + + return modifiedRequest; + } + + protected interceptResponseGET(request: HttpRequest, response: HttpResponse): HttpResponse { + let modifiedResponse: HttpResponse = response; + const urlParts: HostUrlParts = this.getHostUrlParts(request.url); + + // for the "userprofile" endpoint, return just the first profile in the array + if (request.url.match(/userprofile/) && response.body instanceof Array) { + const userProfile: any = response.body[0]; + modifiedResponse = response.clone({ + body: userProfile + }); + } + + // for requests with no path (targeting the root url of the backend) + if (!urlParts.path) { + const xsrfResponseHeaders: HttpHeaders = new HttpHeaders(); + + modifiedResponse = response.clone({ + headers: xsrfResponseHeaders + .set("Access-Control-Allow-Credentials", "true") + .set("Access-Control-Allow-Origin", "http://localhost:3000") + }); + } + + return modifiedResponse; + } + + private normalizeCollectionResponse(httpResponse: HttpResponse): HttpResponse { + let modifiedResponse: HttpResponse = httpResponse; + + if (httpResponse.body instanceof Array) { + const normalizedBody: any = _cloneDeep(httpResponse.body); + + modifiedResponse = httpResponse.clone({ + body: { items: normalizedBody, metadata: {} } // TODO: collection metadata + }); + } + + return modifiedResponse; + } + + protected isGetCollectionRequest(request: HttpRequest): boolean { + if (ENV === "development") { + return request.method === "GET" && request.params.has("mockCollectionRequest"); + } else { + return request.method === "GET"; // on PROD we take all GET requests as GetCollectionRequests + } + } + + protected deepSetUniqueId(item: any, idProperty: string): void { + if (item instanceof Array) { + for (const childItem of item) { + this.deepSetUniqueId(childItem, idProperty); + } + } else if (typeof item === "object") { + if (!item.hasOwnProperty(idProperty)) { + item[idProperty] = _uniqueId(); + } + + Object.keys(item).forEach((subItem: any) => { + this.deepSetUniqueId(item[subItem], idProperty); + }); + } + } + + protected getHostUrlParts(url: string): HostUrlParts { + // Regex to split all the parts of a URL + // https://snippets.aktagon.com/snippets/72-split-a-url-into-protocol-domain-port-and-uri-using-regular-expressions + const regexUrl: RegExp = /(https?:\/\/)?([^:^/]*)(:\d*)?(.*)?/; + const matches: RegExpMatchArray = url.match(regexUrl); + + return { + protocol: matches[1], + domain: matches[2], + port: matches[3], // .replace(":", ""), + path: matches[4] + }; + } + + protected setXSRFCookie(): void { + const xsrfToken: string = this.generateXSRFToken(); + + // FIXME: why can't moment be loaded in this file? + // const cookieExpiration: string = moment() + // .add(40, "m") + // .toDate() + // .toUTCString(); // 40 minutes from now + const expirationDate: Date = new Date(); + expirationDate.setTime(new Date().getTime() + 2400000); // 40 minutes from now + const cookieExpiration: string = expirationDate.toUTCString(); + const cookieAttributes: string[] = [`${this.xsrfCookieName}=${xsrfToken}`, `path='/'`, `expires=${cookieExpiration}`]; + + document.cookie = cookieAttributes.join(";"); + } + + protected generateXSRFToken(): string { + return _uniqueId("xsrf-token-"); + } +} diff --git a/showcase/src/app/in-memory-data/interceptors/index.ts b/showcase/src/app/in-memory-data/interceptors/index.ts new file mode 100644 index 0000000000..871d48c93c --- /dev/null +++ b/showcase/src/app/in-memory-data/interceptors/index.ts @@ -0,0 +1 @@ +export * from "./in-memory-data.interceptor"; diff --git a/showcase/src/app/in-memory-data/services/in-memory-data.service.ts b/showcase/src/app/in-memory-data/services/in-memory-data.service.ts new file mode 100644 index 0000000000..b4f6426885 --- /dev/null +++ b/showcase/src/app/in-memory-data/services/in-memory-data.service.ts @@ -0,0 +1,123 @@ +import { Inject, Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +// using the full path to import the "angular-in-memory-web-api" interfaces to avoid adding "@angular/http" to Showcase npm dependencies! +// see https://github.com/angular/in-memory-web-api/issues/215 +import { + InMemoryDbService, + ParsedRequestUrl, + RequestInfo, + RequestInfoUtilities, + ResponseOptions +} from "angular-in-memory-web-api/interfaces"; +import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core"; + +const _cloneDeep: Function = require("lodash/cloneDeep"); +const mockData: any = require("../../../../config/json-server/data.json"); + +@Injectable() +export class InMemoryDataService implements InMemoryDbService { + public constructor(@Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService) { + /* empty constructor */ + } + + /** + * Method called by the "angular-in-memory-web-api" to create the "database" hash whose keys are collection names and + * whose values are arrays of collection objects to return or update + * @param _reqInfo - The RequestInfo object in case this method is invoked as a result of a POST `commands/resetDb` request + * @returns The "database" object or an Observable of Promise that will return such object asynchronously + * @link https://github.com/angular/in-memory-web-api#basic-setup + */ + public createDb(_reqInfo?: RequestInfo): {} | Observable<{}> | Promise<{}> { + // replace the "uuid" field defined in the mock data by the "id" field expected by the in-memory-db + this.deepReplaceProperty(mockData, "uuid", "id"); + + // IMPORTANT: cannot mock "logging" and "logout" requests since they are performed via XHR and not via Angular + return mockData; + } + + /** + * Custom implementation of the "parseRequestUrl" method which will be called by the "angular-in-memory-web-api" + * @param _url - The request URL + * @param _requestInfoUtils - Some utility methods provided by the "angular-in-memory-web-api" library including the default parser. + * @returns The ParsedRequestUrl object in case the original one was modified or "undefined" to let the service use the default parser. + * @link https://github.com/angular/in-memory-web-api#custom-parserequesturl + */ + public parseRequestUrl(_url: string, _requestInfoUtils: RequestInfoUtilities): ParsedRequestUrl | undefined { + // the default parser is in the RequestInfoUtilities. You can use it by calling _requestInfoUtils.parseRequestUrl(_url)) + return undefined; + } + + /** + * Method to override the default handling of Http GET requests + * @param requestInfo - The RequestInfo object with info about the current request url extracted from an Http Request. + * @returns An observable that will emit the Http response in case it is manually constructed or "undefined" to let the service + * continue with its default processing of the HTTP request. + * @link https://github.com/angular/in-memory-web-api#http-method-interceptors + */ + public get(requestInfo: RequestInfo): Observable | undefined { + if (requestInfo.apiBase === "/" && (requestInfo.collectionName === undefined || requestInfo.collectionName === "")) { + const inMemoryDBResponse: string = `

Congrats!

+

You\'re successfully running Stark in memory database

+
+

+ To access and modify resources, you can use any HTTP method +
+

    +
  • GET
  • +
  • POST
  • +
  • PUT
  • +
  • PATCH
  • +
  • DELETE
  • +

    `; + + return requestInfo.utils.createResponse$(() => { + return { + body: inMemoryDBResponse, + status: 200, + url: requestInfo.url + }; + }); + } else { + return undefined; // do not intercept, let the default GET handle it + } + } + + /** + * Response interceptor to be called by this service. + * This can be used to modify the response before returning it (for example: to add a header that your application is expecting) + * @param response - The intercepted Response object. + * @param _requestInfo - The RequestInfo object with info about the current request url extracted from an Http Request. + * @returns The modified response (if needed) + * @link https://github.com/angular/in-memory-web-api#responseinterceptor + */ + public responseInterceptor(response: ResponseOptions, _requestInfo: RequestInfo): ResponseOptions { + // a full copy of the database can be retrieved by calling _requestInfo.utils.getDb()) + + // replace the "id" field coming from the in-memory-db by the "uuid" field expected by the application + const normalizedBody: any = _cloneDeep(response.body); + this.deepReplaceProperty(normalizedBody, "id", "uuid"); + response.body = normalizedBody; + + return response; + } + + /** + * Replace an object property recursively + */ + protected deepReplaceProperty(item: any, property: string, newProperty: string): void { + if (item instanceof Array) { + for (const childItem of item) { + this.deepReplaceProperty(childItem, property, newProperty); + } + } else if (typeof item === "object") { + if (item.hasOwnProperty(property)) { + item[newProperty] = item[property]; + delete item[property]; + } + + Object.keys(item).forEach((subItem: any) => { + this.deepReplaceProperty(item[subItem], property, newProperty); + }); + } + } +} diff --git a/showcase/src/app/in-memory-data/services/index.ts b/showcase/src/app/in-memory-data/services/index.ts new file mode 100644 index 0000000000..3aae056139 --- /dev/null +++ b/showcase/src/app/in-memory-data/services/index.ts @@ -0,0 +1 @@ +export * from "./in-memory-data.service"; diff --git a/showcase/src/stark-app-config.json b/showcase/src/stark-app-config.json index 079233e98e..8f12538c66 100644 --- a/showcase/src/stark-app-config.json +++ b/showcase/src/stark-app-config.json @@ -10,7 +10,6 @@ "sessionTimeoutWarningPeriod": 20, "keepAliveUrl": "http://localhost:5000/keepalive", "keepAliveInterval": 20, - "keepAliveDisabled": true, "angularDebugInfoEnabled": null, "debugLoggingEnabled": null, "loggingFlushPersistSize": 50, diff --git a/starter/package-lock.json b/starter/package-lock.json index fe9a0cabf6..4bdb1c666f 100644 --- a/starter/package-lock.json +++ b/starter/package-lock.json @@ -961,7 +961,7 @@ "@types/node": "8.10.15", "coveralls": "3.0.2", "istanbul-lib-instrument": "2.3.2", - "jasmine-core": "3.2.1", + "jasmine-core": "3.3.0", "karma": "3.1.1", "karma-chrome-launcher": "2.2.0", "karma-coverage": "1.1.2", @@ -1148,7 +1148,7 @@ "@types/nouislider": "9.0.4", "@types/prismjs": "1.9.0", "normalize.css": "8.0.0", - "nouislider": "12.0.0", + "nouislider": "12.1.0", "prettier": "1.14.3", "pretty-data": "0.40.0", "prismjs": "1.15.0" @@ -7632,9 +7632,9 @@ } }, "jasmine-core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.2.1.tgz", - "integrity": "sha512-pa9tbBWgU0EE4SWgc85T4sa886ufuQdsgruQANhECYjwqgV4z7Vw/499aCaP8ZH79JDS4vhm8doDG9HO4+e4sA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.3.0.tgz", + "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==", "dev": true }, "jasmine-diff": { @@ -9599,9 +9599,9 @@ "integrity": "sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A==" }, "nouislider": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-12.0.0.tgz", - "integrity": "sha512-wUr7AJwDQ2oraHgNjLnELjvghTJrKskELowAhdyk01zN6jpXegoyVQJL2Tzt/hp9qwnx+E5KwWS4RNbIMizVcw==" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-12.1.0.tgz", + "integrity": "sha512-SAOabF6hBm8201c6LDbkVOVhgwY49+/ms72ZLUF2qkN5RCf7FfUvEh/hGZ7XcwZHU+I/grlicPmcSk1/rrMnOw==" }, "npm-package-arg": { "version": "6.1.0",