Initalize Project
Some checks failed
Quality Check / Linting (push) Failing after 26s

This commit is contained in:
Dominik Säume 2024-12-18 09:35:34 +01:00
parent a6cf0429aa
commit 5bf3ea88ed
36 changed files with 18821 additions and 1 deletions

28
.forgejo/workflows/qs.yml Normal file
View file

@ -0,0 +1,28 @@
name: "Quality Check"
on:
- push
- pull_request
jobs:
linting:
name: "Linting"
runs-on: "ubuntu-latest"
container:
image: "git.euph.dev/actions/runner-basic:latest"
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- uses: actions/cache@v3
with:
path: ~/.node_modules
key: ${{ runner.os }}-${{ hashFiles('package-lock.json', 'eslint.config.mjs', '.stylelintrc', '.stylelintignore') }}
restore-keys: ${{ runner.os }}-
- name: "NPM install"
run: npm i
- name: "Linting TS"
run: npm run lint:ts
- name: "Linting SCSS"
run: npm run lint:scss

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
node_modules/
.angular/
.idea/
.vscode/

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
*

3
.stylelintignore Normal file
View file

@ -0,0 +1,3 @@
*.*
!*.css
!*.scss

14
.stylelintrc.json Normal file
View file

@ -0,0 +1,14 @@
{
"extends": "stylelint-config-standard-scss",
"plugins": [
"stylelint-scss"
],
"rules": {
"custom-property-empty-line-before": null,
"declaration-empty-line-before": null,
"media-feature-range-notation": null,
"import-notation": "string",
"scss/no-global-function-names": null,
"no-empty-source": null
}
}

13
Justfile Normal file
View file

@ -0,0 +1,13 @@
_choose:
just --choose
up:
docker compose up -d
ng serve
down:
docker compose down
lint:
-npm run lint:ts:fix
-npm run lint:scss:fix

View file

@ -1,3 +1,18 @@
![QS Badge](https://git.euph.dev//SZUT/EMS//actions/workflows/qs.yml/badge.svg?branch=trunk)
# EMS # EMS
https://hb.itslearning.com/LearningToolElement/ViewLearningToolElement.aspx?LearningToolElementId=21843391 ## Links
- [Ausgangslage](https://hb.itslearning.com/LearningToolElement/ViewLearningToolElement.aspx?LearningToolElementId=21843391)
## Setup
> [!NOTE]
> Dieses Projekt hat ein [Justfile](https://just.systems)
1. Projekt Clonen
1. NPM install
1. Docker Container Hochfahren
1. Angular App starten

102
angular.json Normal file
View file

@ -0,0 +1,102 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"a": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/a",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "a:build:production"
},
"development": {
"buildTarget": "a:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

27
compose.yml Normal file
View file

@ -0,0 +1,27 @@
services:
postgres:
container_name: ems_postgres
image: postgres:17.2
volumes:
- ems_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: employee_db
POSTGRES_USER: employee
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
api:
container_name: ems_api
image: berndheidemann/employee-management-service:1.1.3.1
depends_on:
- postgres
environment:
spring.datasource.url: jdbc:postgresql://ems_postgres:5432/employee_db
spring.datasource.username: employee
spring.datasource.password: secret
ports:
- "8080:8089"
volumes:
ems_data:

63
eslint.config.mjs Normal file
View file

@ -0,0 +1,63 @@
import simpleImportSort from "eslint-plugin-simple-import-sort";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import {fileURLToPath} from "node:url";
import js from "@eslint/js";
import {FlatCompat} from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default [
{
ignores: ["src/app/core/ems"],
},
...compat.extends("eslint:recommended", "plugin:@typescript-eslint/strict"),
{
plugins: {
"simple-import-sort": simpleImportSort,
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
globals: {
...globals.browser,
},
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
indent: ["error", 4],
"linebreak-style": ["error", "unix"],
quotes: ["error", "single"],
semi: ["error", "always"],
strict: "error",
"array-bracket-newline": "error",
yoda: "error",
"@typescript-eslint/array-type": [
"error",
{
default: "generic",
},
],
"@typescript-eslint/ban-tslint-comment": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-extraneous-class": "off",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"no-mixed-spaces-and-tabs": "off",
},
},
];

4
getBearerToken.http Normal file
View file

@ -0,0 +1,4 @@
POST http://authproxy.szut.dev
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=employee-management-service&username=user&password=test

16934
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

55
package.json Normal file
View file

@ -0,0 +1,55 @@
{
"name": "a",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"lint:ts": "eslint src",
"lint:scss": "stylelint src",
"lint:ts:fix": "eslint src --fix",
"lint:scss:fix": "stylelint src --fix"
},
"private": true,
"dependencies": {
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.2",
"@angular/cli": "^19.0.2",
"@angular/compiler-cli": "^19.0.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@hey-api/openapi-ts": "^0.52.9",
"@types/jasmine": "~5.1.0",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"eslint": "^9.17.0",
"eslint-plugin-autofix": "^2.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.13.0",
"jasmine-core": "~5.4.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"stylelint": "^16.12.0",
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-scss": "^6.10.0",
"typescript": "~5.6.2"
},
"volta": {
"node": "22.12.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,8 @@
@for (employee of $employees | async ; track employee.id) {
<code style="white-space: pre-wrap">
{{ employee | json}}
</code>
<hr>
}
<router-outlet />

View file

20
src/app/app.component.ts Normal file
View file

@ -0,0 +1,20 @@
import {AsyncPipe, JsonPipe} from '@angular/common';
import {Component, OnInit} from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { EmployeeControllerService, FindAll1Response } from '@core/ems/index';
import { Observable } from 'rxjs';
@Component({
selector: 'app-root',
imports: [RouterOutlet, JsonPipe, AsyncPipe],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent{
title = 'EMS';
readonly $employees: Observable<FindAll1Response>;
constructor(private apiClient: EmployeeControllerService) {
this.$employees = this.apiClient.findAll1();
}
}

17
src/app/app.config.ts Normal file
View file

@ -0,0 +1,17 @@
import { provideHttpClient } from '@angular/common/http';
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter } from '@angular/router';
import { routes } from '@app/app.routes';
import { OpenAPI } from '@core/ems/core/OpenAPI';
OpenAPI.BASE = 'http://localhost:8080';
OpenAPI.TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzUFQ0dldiNno5MnlQWk1EWnBqT1U0RjFVN0lwNi1ELUlqQWVGczJPbGU0In0.eyJleHAiOjE3MzQ1MTY5MTQsImlhdCI6MTczNDUxMzMxNCwianRpIjoiMjk2MzAwNTQtMWI2Ni00ODIyLWFiMGItNmI5NjlkZDg4MTRkIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5zenV0LmRldi9hdXRoL3JlYWxtcy9zenV0IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjU1NDZjZDIxLTk4NTQtNDMyZi1hNDY3LTRkZTNlZWRmNTg4OSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImVtcGxveWVlLW1hbmFnZW1lbnQtc2VydmljZSIsInNlc3Npb25fc3RhdGUiOiJhMDIzMmNhMy0yZjM3LTQ3OTctYThkMC0zNjJhNzBlODY1MDMiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NDIwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsicHJvZHVjdF9vd25lciIsIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1zenV0IiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIifQ.evYkQetnwJWgzHjSRrZyN1Ls-fpdBsM0KYwp0RKcsgrg4w4p-WMnFGDch_1kFWflMq9i3J_sAhsPcFhvCiKEpo5xca9yj0-20GMUZXH7aNwCneqGur2f9wh3k8nQdoYsuwKTfKpGSsNWTQ20rV7CqHIne-lD2rayhb2wHwOsbklsSf0K9s37EKPwASfln4g1KGTbBpwFDz8p75Vhr5HqlaCJUxN-NMYKM41_2ao8ykhCVkKRk7cZwjKfB3MOk5Xk8l0NHcPBp8G_pOyrjyqC-fGekcxEF1ucYSWqw4r_PsUTeLkO8M-RK0h798JWq7l-OcODgxr4qhx-ungUPnGFxw';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
provideAnimationsAsync()
],
};

3
src/app/app.routes.ts Normal file
View file

@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

View file

@ -0,0 +1,11 @@
import {defineConfig} from '@hey-api/openapi-ts';
export default defineConfig({
input: 'src/app/core/ems/ems.yml',
output: 'src/app/core/ems',
schemas: false,
services: {
asClass: true
},
client: 'angular',
});

View file

@ -0,0 +1,21 @@
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
export class ApiError extends Error {
public readonly url: string;
public readonly status: number;
public readonly statusText: string;
public readonly body: unknown;
public readonly request: ApiRequestOptions;
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
super(message);
this.name = 'ApiError';
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
}

View file

@ -0,0 +1,21 @@
export type ApiRequestOptions<T = unknown> = {
readonly body?: any;
readonly cookies?: Record<string, unknown>;
readonly errors?: Record<number | string, string>;
readonly formData?: Record<string, unknown> | any[] | Blob | File;
readonly headers?: Record<string, unknown>;
readonly mediaType?: string;
readonly method:
| 'DELETE'
| 'GET'
| 'HEAD'
| 'OPTIONS'
| 'PATCH'
| 'POST'
| 'PUT';
readonly path?: Record<string, unknown>;
readonly query?: Record<string, unknown>;
readonly responseHeader?: string;
readonly responseTransformer?: (data: unknown) => Promise<T>;
readonly url: string;
};

View file

@ -0,0 +1,7 @@
export type ApiResult<TData = any> = {
readonly body: TData;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly url: string;
};

View file

@ -0,0 +1,55 @@
import type { HttpResponse } from '@angular/common/http';
import type { ApiRequestOptions } from './ApiRequestOptions';
type Headers = Record<string, string>;
type Middleware<T> = (value: T) => T | Promise<T>;
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export class Interceptors<T> {
_fns: Middleware<T>[];
constructor() {
this._fns = [];
}
eject(fn: Middleware<T>): void {
const index = this._fns.indexOf(fn);
if (index !== -1) {
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
}
}
use(fn: Middleware<T>): void {
this._fns = [...this._fns, fn];
}
}
export type OpenAPIConfig = {
BASE: string;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
ENCODE_PATH?: ((path: string) => string) | undefined;
HEADERS?: Headers | Resolver<Headers> | undefined;
PASSWORD?: string | Resolver<string> | undefined;
TOKEN?: string | Resolver<string> | undefined;
USERNAME?: string | Resolver<string> | undefined;
VERSION: string;
WITH_CREDENTIALS: boolean;
interceptors: {
response: Interceptors<HttpResponse<any>>;
};
};
export const OpenAPI: OpenAPIConfig = {
BASE: '',
CREDENTIALS: 'include',
ENCODE_PATH: undefined,
HEADERS: undefined,
PASSWORD: undefined,
TOKEN: undefined,
USERNAME: undefined,
VERSION: '1.1.2',
WITH_CREDENTIALS: false,
interceptors: {
response: new Interceptors(),
},
};

View file

@ -0,0 +1,337 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import type { HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { forkJoin, of, throwError } from 'rxjs';
import { catchError, map, switchMap } from 'rxjs/operators';
import type { Observable } from 'rxjs';
import { ApiError } from './ApiError';
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
import type { OpenAPIConfig } from './OpenAPI';
export const isString = (value: unknown): value is string => {
return typeof value === 'string';
};
export const isStringWithValue = (value: unknown): value is string => {
return isString(value) && value !== '';
};
export const isBlob = (value: any): value is Blob => {
return value instanceof Blob;
};
export const isFormData = (value: unknown): value is FormData => {
return value instanceof FormData;
};
export const base64 = (str: string): string => {
try {
return btoa(str);
} catch (err) {
// @ts-ignore
return Buffer.from(str).toString('base64');
}
};
export const getQueryString = (params: Record<string, unknown>): string => {
const qs: string[] = [];
const append = (key: string, value: unknown) => {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
};
const encodePair = (key: string, value: unknown) => {
if (value === undefined || value === null) {
return;
}
if (value instanceof Date) {
append(key, value.toISOString());
} else if (Array.isArray(value)) {
value.forEach(v => encodePair(key, v));
} else if (typeof value === 'object') {
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
} else {
append(key, value);
}
};
Object.entries(params).forEach(([key, value]) => encodePair(key, value));
return qs.length ? `?${qs.join('&')}` : '';
};
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
const encoder = config.ENCODE_PATH || encodeURI;
const path = options.url
.replace('{api-version}', config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(group)) {
return encoder(String(options.path[group]));
}
return substring;
});
const url = config.BASE + path;
return options.query ? url + getQueryString(options.query) : url;
};
export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
if (options.formData) {
const formData = new FormData();
const process = (key: string, value: unknown) => {
if (isString(value) || isBlob(value)) {
formData.append(key, value);
} else {
formData.append(key, JSON.stringify(value));
}
};
Object.entries(options.formData)
.filter(([, value]) => value !== undefined && value !== null)
.forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => process(key, v));
} else {
process(key, value);
}
});
return formData;
}
return undefined;
};
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export const resolve = async <T>(options: ApiRequestOptions<T>, resolver?: T | Resolver<T>): Promise<T | undefined> => {
if (typeof resolver === 'function') {
return (resolver as Resolver<T>)(options);
}
return resolver;
};
export const getHeaders = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>): Observable<HttpHeaders> => {
return forkJoin({
// @ts-ignore
token: resolve(options, config.TOKEN),
// @ts-ignore
username: resolve(options, config.USERNAME),
// @ts-ignore
password: resolve(options, config.PASSWORD),
// @ts-ignore
additionalHeaders: resolve(options, config.HEADERS),
}).pipe(
map(({ token, username, password, additionalHeaders }) => {
const headers = Object.entries({
Accept: 'application/json',
...additionalHeaders,
...options.headers,
})
.filter(([, value]) => value !== undefined && value !== null)
.reduce((headers, [key, value]) => ({
...headers,
[key]: String(value),
}), {} as Record<string, string>);
if (isStringWithValue(token)) {
headers['Authorization'] = `Bearer ${token}`;
}
if (isStringWithValue(username) && isStringWithValue(password)) {
const credentials = base64(`${username}:${password}`);
headers['Authorization'] = `Basic ${credentials}`;
}
if (options.body !== undefined) {
if (options.mediaType) {
headers['Content-Type'] = options.mediaType;
} else if (isBlob(options.body)) {
headers['Content-Type'] = options.body.type || 'application/octet-stream';
} else if (isString(options.body)) {
headers['Content-Type'] = 'text/plain';
} else if (!isFormData(options.body)) {
headers['Content-Type'] = 'application/json';
}
}
return new HttpHeaders(headers);
}),
);
};
export const getRequestBody = (options: ApiRequestOptions): unknown => {
if (options.body) {
if (options.mediaType?.includes('application/json') || options.mediaType?.includes('+json')) {
return JSON.stringify(options.body);
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
return options.body;
} else {
return JSON.stringify(options.body);
}
}
return undefined;
};
export const sendRequest = <T>(
config: OpenAPIConfig,
options: ApiRequestOptions<T>,
http: HttpClient,
url: string,
body: unknown,
formData: FormData | undefined,
headers: HttpHeaders
): Observable<HttpResponse<T>> => {
return http.request<T>(options.method, url, {
headers,
body: body ?? formData,
withCredentials: config.WITH_CREDENTIALS,
observe: 'response',
});
};
export const getResponseHeader = <T>(response: HttpResponse<T>, responseHeader?: string): string | undefined => {
if (responseHeader) {
const value = response.headers.get(responseHeader);
if (isString(value)) {
return value;
}
}
return undefined;
};
export const getResponseBody = <T>(response: HttpResponse<T>): T | undefined => {
if (response.status !== 204 && response.body !== null) {
return response.body;
}
return undefined;
};
export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
const errors: Record<number, string> = {
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Payload Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: 'Im a teapot',
421: 'Misdirected Request',
422: 'Unprocessable Content',
423: 'Locked',
424: 'Failed Dependency',
425: 'Too Early',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
510: 'Not Extended',
511: 'Network Authentication Required',
...options.errors,
}
const error = errors[result.status];
if (error) {
throw new ApiError(options, result, error);
}
if (!result.ok) {
const errorStatus = result.status ?? 'unknown';
const errorStatusText = result.statusText ?? 'unknown';
const errorBody = (() => {
try {
return JSON.stringify(result.body, null, 2);
} catch (e) {
return undefined;
}
})();
throw new ApiError(options, result,
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
);
}
};
/**
* Request method
* @param config The OpenAPI configuration object
* @param http The Angular HTTP client
* @param options The request options from the service
* @returns Observable<T>
* @throws ApiError
*/
export const request = <T>(config: OpenAPIConfig, http: HttpClient, options: ApiRequestOptions<T>): Observable<T> => {
const url = getUrl(config, options);
const formData = getFormData(options);
const body = getRequestBody(options);
return getHeaders(config, options).pipe(
switchMap(headers => {
return sendRequest<T>(config, options, http, url, body, formData, headers);
}),
switchMap(async response => {
for (const fn of config.interceptors.response._fns) {
response = await fn(response);
}
const responseBody = getResponseBody(response);
const responseHeader = getResponseHeader(response, options.responseHeader);
let transformedBody = responseBody;
if (options.responseTransformer && response.ok) {
transformedBody = await options.responseTransformer(responseBody)
}
return {
url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
body: responseHeader ?? transformedBody,
} as ApiResult;
}),
catchError((error: HttpErrorResponse) => {
if (!error.status) {
return throwError(() => error);
}
return of({
url,
ok: error.ok,
status: error.status,
statusText: error.statusText,
body: error.error ?? error.statusText,
} as ApiResult);
}),
map(result => {
catchErrorCodes(options, result);
return result.body as T;
}),
catchError((error: ApiError) => {
return throwError(() => error);
}),
);
};

493
src/app/core/ems/ems.yml Normal file
View file

@ -0,0 +1,493 @@
openapi: 3.0.1
info:
title: Employees Management Micro-Service
description: "\n## Overview\n\nEmployees Management Service API manages the employees\
\ of HighTec Gmbh including their qualifications. It offers the possibility to\
\ create, read, update and delete employees and qualifications. Existing employees\
\ can be assigned new qualifications or have them withdrawn. \nThe API is organized\
\ around REST. It has predictable resource-oriented URLs, accepts JSON-encoded\
\ request bodies, returns JSON-encoded responses, uses standard HTTP response\
\ codes and authentication.\n"
version: 1.1.2
servers:
- url: ""
security:
- bearerAuth: []
paths:
/qualifications/{id}:
put:
tags:
- qualification-controller
summary: updates a qualification
operationId: updateQualification
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/QualificationPostDTO'
required: true
responses:
"200":
description: updated qualification
content:
application/json:
schema:
$ref: '#/components/schemas/QualificationPostDTO'
"401":
description: not authorized
"404":
description: resource not found
"400":
description: invalid JSON posted
delete:
tags:
- qualification-controller
summary: deletes a qualification by id
operationId: deleteQualificationByDesignation
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
"401":
description: not authorized
"404":
description: resource not found
"204":
description: delete successful
"403":
description: qualification is in use
/employees/{id}:
get:
tags:
- employee-controller
summary: find employee by id
operationId: findById
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
"200":
description: employee
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeResponseDTO'
"401":
description: not authorized
"404":
description: resource not found
put:
tags:
- employee-controller
summary: updates employee by id
operationId: updateEmployee
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeRequestPutDTO'
required: true
responses:
"200":
description: employee
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeResponseDTO'
"401":
description: not authorized
"404":
description: resource not found
delete:
tags:
- employee-controller
summary: deletes a employee by id
operationId: deleteCustomer
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
"401":
description: not authorized
"404":
description: resource not found
"204":
description: delete successful
patch:
tags:
- employee-controller
summary: updates employee by id
operationId: patchEmployee
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeRequestPutDTO'
required: true
responses:
"200":
description: employee
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeResponseDTO'
"401":
description: not authorized
"404":
description: resource not found
/qualifications:
get:
tags:
- qualification-controller
summary: delivers a list of all available qualifications
operationId: findAll
responses:
"401":
description: not authorized
"200":
description: list of qualifications
content:
application/json:
schema:
$ref: '#/components/schemas/QualificationGetDTO'
post:
tags:
- qualification-controller
summary: creates a new qualification with its id and designation
operationId: createQualification
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/QualificationPostDTO'
required: true
responses:
"201":
description: created qualification
content:
application/json:
schema:
$ref: '#/components/schemas/QualificationPostDTO'
"401":
description: not authorized
"400":
description: invalid JSON posted
/employees:
get:
tags:
- employee-controller
summary: delivers a list of all employees
operationId: findAll_1
responses:
"401":
description: not authorized
"200":
description: list of employees
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/EmployeeResponseDTO'
post:
tags:
- employee-controller
summary: creates a new employee
operationId: createEmployee
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeRequestDTO'
required: true
responses:
"401":
description: not authorized
"201":
description: created employee
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeResponseDTO'
"400":
description: invalid JSON posted
/employees/{id}/qualifications:
get:
tags:
- employee-controller
summary: finds all qualifications of an employee by id
operationId: findAllQualificationOfAEmployeeById
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
"401":
description: not authorized
"404":
description: resource not found
"200":
description: employee with a list of his qualifications
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeNameAndSkillDataDTO'
post:
tags:
- employee-controller
summary: adds a qualification to an employee by id
operationId: addQualificationToEmployeeById
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/QualificationPostDTO'
required: true
responses:
"401":
description: not authorized
"404":
description: resource not found
"400":
description: invalid JSON posted or employee already has this qualification
"200":
description: employee with a list of his qualifications
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeNameAndSkillDataDTO'
/qualifications/{id}/employees:
get:
tags:
- qualification-controller
summary: find employees by qualification id
operationId: findAllEmployeesByQualification
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
"401":
description: not authorized
"200":
description: List of employees who have the desired qualification
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeesForAQualificationDTO'
"404":
description: qualification id does not exist
/employees/{eid}/qualifications/{qid}:
delete:
tags:
- employee-controller
summary: deletes a qualification of an employee by id
operationId: removeQualificationFromEmployee
parameters:
- name: eid
in: path
required: true
schema:
type: integer
format: int64
- name: qid
in: path
required: true
schema:
type: integer
format: int64
responses:
"401":
description: not authorized
"404":
description: resource not found
"200":
description: employee with a list of his qualifications
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeNameAndSkillDataDTO'
components:
schemas:
QualificationPostDTO:
required:
- skill
type: object
properties:
skill:
type: string
EmployeeRequestPutDTO:
type: object
properties:
lastName:
type: string
firstName:
type: string
street:
type: string
postcode:
type: string
city:
type: string
phone:
type: string
skillSet:
type: array
items:
type: integer
format: int64
EmployeeResponseDTO:
required:
- city
- firstName
- lastName
- phone
- postcode
- street
type: object
properties:
id:
type: integer
format: int64
lastName:
type: string
firstName:
type: string
street:
type: string
postcode:
maxLength: 5
minLength: 5
type: string
city:
type: string
phone:
type: string
skillSet:
type: array
items:
$ref: '#/components/schemas/QualificationGetDTO'
QualificationGetDTO:
type: object
properties:
skill:
type: string
id:
type: integer
format: int64
EmployeeRequestDTO:
required:
- city
- firstName
- lastName
- phone
- postcode
- street
type: object
properties:
lastName:
type: string
firstName:
type: string
street:
type: string
postcode:
maxLength: 5
minLength: 5
type: string
city:
type: string
phone:
type: string
skillSet:
type: array
items:
type: integer
format: int64
EmployeeNameAndSkillDataDTO:
type: object
properties:
id:
type: integer
format: int64
lastName:
type: string
firstName:
type: string
skillSet:
uniqueItems: true
type: array
items:
$ref: '#/components/schemas/QualificationGetDTO'
EmployeeNameDataDTO:
type: object
properties:
id:
type: integer
format: int64
lastName:
type: string
firstName:
type: string
EmployeesForAQualificationDTO:
type: object
properties:
qualification:
$ref: '#/components/schemas/QualificationGetDTO'
employees:
uniqueItems: true
type: array
items:
$ref: '#/components/schemas/EmployeeNameDataDTO'
securitySchemes:
bearerAuth:
type: http
name: bearerAuth
scheme: bearer
bearerFormat: JWT

View file

@ -0,0 +1,5 @@
// This file is auto-generated by @hey-api/openapi-ts
export { ApiError } from './core/ApiError';
export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
export * from './services.gen';
export * from './types.gen';

View file

@ -0,0 +1,321 @@
// This file is auto-generated by @hey-api/openapi-ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import type { Observable } from 'rxjs';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { UpdateQualificationData, UpdateQualificationResponse, DeleteQualificationByDesignationData, DeleteQualificationByDesignationResponse, FindAllResponse, CreateQualificationData, CreateQualificationResponse, FindAllEmployeesByQualificationData, FindAllEmployeesByQualificationResponse, FindByIdData, FindByIdResponse, UpdateEmployeeData, UpdateEmployeeResponse, DeleteCustomerData, DeleteCustomerResponse, PatchEmployeeData, PatchEmployeeResponse, FindAll1Response, CreateEmployeeData, CreateEmployeeResponse, FindAllQualificationOfAemployeeByIdData, FindAllQualificationOfAemployeeByIdResponse, AddQualificationToEmployeeByIdData, AddQualificationToEmployeeByIdResponse, RemoveQualificationFromEmployeeData, RemoveQualificationFromEmployeeResponse } from './types.gen';
@Injectable({
providedIn: 'root'
})
export class QualificationControllerService {
constructor(public readonly http: HttpClient) { }
/**
* updates a qualification
* @param data The data for the request.
* @param data.id
* @param data.requestBody
* @returns QualificationPostDTO updated qualification
* @throws ApiError
*/
public updateQualification(data: UpdateQualificationData): Observable<UpdateQualificationResponse> {
return __request(OpenAPI, this.http, {
method: 'PUT',
url: '/qualifications/{id}',
path: {
id: data.id
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
400: 'invalid JSON posted',
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* deletes a qualification by id
* @param data The data for the request.
* @param data.id
* @returns void delete successful
* @throws ApiError
*/
public deleteQualificationByDesignation(data: DeleteQualificationByDesignationData): Observable<DeleteQualificationByDesignationResponse> {
return __request(OpenAPI, this.http, {
method: 'DELETE',
url: '/qualifications/{id}',
path: {
id: data.id
},
errors: {
401: 'not authorized',
403: 'qualification is in use',
404: 'resource not found'
}
});
}
/**
* delivers a list of all available qualifications
* @returns QualificationGetDTO list of qualifications
* @throws ApiError
*/
public findAll(): Observable<FindAllResponse> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/qualifications',
errors: {
401: 'not authorized'
}
});
}
/**
* creates a new qualification with its id and designation
* @param data The data for the request.
* @param data.requestBody
* @returns QualificationPostDTO created qualification
* @throws ApiError
*/
public createQualification(data: CreateQualificationData): Observable<CreateQualificationResponse> {
return __request(OpenAPI, this.http, {
method: 'POST',
url: '/qualifications',
body: data.requestBody,
mediaType: 'application/json',
errors: {
400: 'invalid JSON posted',
401: 'not authorized'
}
});
}
/**
* find employees by qualification id
* @param data The data for the request.
* @param data.id
* @returns EmployeesForAQualificationDTO List of employees who have the desired qualification
* @throws ApiError
*/
public findAllEmployeesByQualification(data: FindAllEmployeesByQualificationData): Observable<FindAllEmployeesByQualificationResponse> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/qualifications/{id}/employees',
path: {
id: data.id
},
errors: {
401: 'not authorized',
404: 'qualification id does not exist'
}
});
}
}
@Injectable({
providedIn: 'root'
})
export class EmployeeControllerService {
constructor(public readonly http: HttpClient) { }
/**
* find employee by id
* @param data The data for the request.
* @param data.id
* @returns EmployeeResponseDTO employee
* @throws ApiError
*/
public findById(data: FindByIdData): Observable<FindByIdResponse> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/employees/{id}',
path: {
id: data.id
},
errors: {
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* updates employee by id
* @param data The data for the request.
* @param data.id
* @param data.requestBody
* @returns EmployeeResponseDTO employee
* @throws ApiError
*/
public updateEmployee(data: UpdateEmployeeData): Observable<UpdateEmployeeResponse> {
return __request(OpenAPI, this.http, {
method: 'PUT',
url: '/employees/{id}',
path: {
id: data.id
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* deletes a employee by id
* @param data The data for the request.
* @param data.id
* @returns void delete successful
* @throws ApiError
*/
public deleteCustomer(data: DeleteCustomerData): Observable<DeleteCustomerResponse> {
return __request(OpenAPI, this.http, {
method: 'DELETE',
url: '/employees/{id}',
path: {
id: data.id
},
errors: {
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* updates employee by id
* @param data The data for the request.
* @param data.id
* @param data.requestBody
* @returns EmployeeResponseDTO employee
* @throws ApiError
*/
public patchEmployee(data: PatchEmployeeData): Observable<PatchEmployeeResponse> {
return __request(OpenAPI, this.http, {
method: 'PATCH',
url: '/employees/{id}',
path: {
id: data.id
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* delivers a list of all employees
* @returns EmployeeResponseDTO list of employees
* @throws ApiError
*/
public findAll1(): Observable<FindAll1Response> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/employees',
errors: {
401: 'not authorized'
}
});
}
/**
* creates a new employee
* @param data The data for the request.
* @param data.requestBody
* @returns EmployeeResponseDTO created employee
* @throws ApiError
*/
public createEmployee(data: CreateEmployeeData): Observable<CreateEmployeeResponse> {
return __request(OpenAPI, this.http, {
method: 'POST',
url: '/employees',
body: data.requestBody,
mediaType: 'application/json',
errors: {
400: 'invalid JSON posted',
401: 'not authorized'
}
});
}
/**
* finds all qualifications of an employee by id
* @param data The data for the request.
* @param data.id
* @returns EmployeeNameAndSkillDataDTO employee with a list of his qualifications
* @throws ApiError
*/
public findAllQualificationOfAemployeeById(data: FindAllQualificationOfAemployeeByIdData): Observable<FindAllQualificationOfAemployeeByIdResponse> {
return __request(OpenAPI, this.http, {
method: 'GET',
url: '/employees/{id}/qualifications',
path: {
id: data.id
},
errors: {
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* adds a qualification to an employee by id
* @param data The data for the request.
* @param data.id
* @param data.requestBody
* @returns EmployeeNameAndSkillDataDTO employee with a list of his qualifications
* @throws ApiError
*/
public addQualificationToEmployeeById(data: AddQualificationToEmployeeByIdData): Observable<AddQualificationToEmployeeByIdResponse> {
return __request(OpenAPI, this.http, {
method: 'POST',
url: '/employees/{id}/qualifications',
path: {
id: data.id
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
400: 'invalid JSON posted or employee already has this qualification',
401: 'not authorized',
404: 'resource not found'
}
});
}
/**
* deletes a qualification of an employee by id
* @param data The data for the request.
* @param data.eid
* @param data.qid
* @returns EmployeeNameAndSkillDataDTO employee with a list of his qualifications
* @throws ApiError
*/
public removeQualificationFromEmployee(data: RemoveQualificationFromEmployeeData): Observable<RemoveQualificationFromEmployeeResponse> {
return __request(OpenAPI, this.http, {
method: 'DELETE',
url: '/employees/{eid}/qualifications/{qid}',
path: {
eid: data.eid,
qid: data.qid
},
errors: {
401: 'not authorized',
404: 'resource not found'
}
});
}
}

View file

@ -0,0 +1,140 @@
// This file is auto-generated by @hey-api/openapi-ts
export type QualificationPostDTO = {
skill: string;
};
export type EmployeeRequestPutDTO = {
lastName?: string;
firstName?: string;
street?: string;
postcode?: string;
city?: string;
phone?: string;
skillSet?: Array<(number)>;
};
export type EmployeeResponseDTO = {
id?: number;
lastName: string;
firstName: string;
street: string;
postcode: string;
city: string;
phone: string;
skillSet?: Array<QualificationGetDTO>;
};
export type QualificationGetDTO = {
skill?: string;
id?: number;
};
export type EmployeeRequestDTO = {
lastName: string;
firstName: string;
street: string;
postcode: string;
city: string;
phone: string;
skillSet?: Array<(number)>;
};
export type EmployeeNameAndSkillDataDTO = {
id?: number;
lastName?: string;
firstName?: string;
skillSet?: Array<QualificationGetDTO>;
};
export type EmployeeNameDataDTO = {
id?: number;
lastName?: string;
firstName?: string;
};
export type EmployeesForAQualificationDTO = {
qualification?: QualificationGetDTO;
employees?: Array<EmployeeNameDataDTO>;
};
export type UpdateQualificationData = {
id: number;
requestBody: QualificationPostDTO;
};
export type UpdateQualificationResponse = (QualificationPostDTO);
export type DeleteQualificationByDesignationData = {
id: number;
};
export type DeleteQualificationByDesignationResponse = (void);
export type FindAllResponse = (QualificationGetDTO);
export type CreateQualificationData = {
requestBody: QualificationPostDTO;
};
export type CreateQualificationResponse = (QualificationPostDTO);
export type FindAllEmployeesByQualificationData = {
id: number;
};
export type FindAllEmployeesByQualificationResponse = (EmployeesForAQualificationDTO);
export type FindByIdData = {
id: number;
};
export type FindByIdResponse = (EmployeeResponseDTO);
export type UpdateEmployeeData = {
id: number;
requestBody: EmployeeRequestPutDTO;
};
export type UpdateEmployeeResponse = (EmployeeResponseDTO);
export type DeleteCustomerData = {
id: number;
};
export type DeleteCustomerResponse = (void);
export type PatchEmployeeData = {
id: number;
requestBody: EmployeeRequestPutDTO;
};
export type PatchEmployeeResponse = (EmployeeResponseDTO);
export type FindAll1Response = (Array<EmployeeResponseDTO>);
export type CreateEmployeeData = {
requestBody: EmployeeRequestDTO;
};
export type CreateEmployeeResponse = (EmployeeResponseDTO);
export type FindAllQualificationOfAemployeeByIdData = {
id: number;
};
export type FindAllQualificationOfAemployeeByIdResponse = (EmployeeNameAndSkillDataDTO);
export type AddQualificationToEmployeeByIdData = {
id: number;
requestBody: QualificationPostDTO;
};
export type AddQualificationToEmployeeByIdResponse = (EmployeeNameAndSkillDataDTO);
export type RemoveQualificationFromEmployeeData = {
eid: number;
qid: number;
};
export type RemoveQualificationFromEmployeeResponse = (EmployeeNameAndSkillDataDTO);

0
src/app/types/.gitkeep Normal file
View file

13
src/index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

7
src/main.ts Normal file
View file

@ -0,0 +1,7 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

1
src/styles.scss Normal file
View file

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

15
tsconfig.app.json Normal file
View file

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

46
tsconfig.json Normal file
View file

@ -0,0 +1,46 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
],
"paths": {
"@/*": [
"./*"
],
"@app/*": [
"./src/app/*"
],
"@dt/*": [
"./src/app/types/*"
],
"@core/*": [
"./src/app/core/*"
]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

15
tsconfig.spec.json Normal file
View file

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}