├── client
├── src
│ ├── assets
│ │ └── .gitkeep
│ ├── app
│ │ ├── app.component.scss
│ │ ├── app.component.html
│ │ ├── app-routing.module.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── app.component.spec.ts
│ │ └── angular-material
│ │ │ └── angular-material.module.ts
│ ├── favicon.ico
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── main.ts
│ ├── index.html
│ ├── test.ts
│ ├── styles.scss
│ └── polyfills.ts
├── proxy.conf.json
├── .editorconfig
├── tsconfig.app.json
├── e2e
│ ├── src
│ │ ├── app.po.ts
│ │ └── app.e2e-spec.ts
│ ├── tsconfig.json
│ └── protractor.conf.js
├── tsconfig.spec.json
├── tsconfig.json
├── .gitignore
├── .browserslistrc
├── README.md
├── .eslintrc.js
├── karma.conf.js
├── package.json
├── tslint.json
└── angular.json
├── .commit-template
├── server
├── .eslintignore
├── nest-cli.json
├── src
│ ├── main.ts
│ ├── abstracts
│ │ ├── index.ts
│ │ ├── base.model.abstract.ts
│ │ └── base.service.abstract.ts
│ ├── app.service.ts
│ ├── filters
│ │ ├── index.ts
│ │ ├── kb-not-found-exception.filter.spec.ts
│ │ ├── kb-validation-exception.filter.spec.ts
│ │ ├── kb-not-found-exception.filter.ts
│ │ └── kb-validation-exception.filter.ts
│ ├── models
│ │ ├── index.ts
│ │ ├── api.model.ts
│ │ ├── public-error.model.ts
│ │ └── product.model.ts
│ ├── api
│ │ ├── index.ts
│ │ ├── api.module.ts
│ │ ├── product
│ │ │ ├── product.module.ts
│ │ │ ├── product.service.ts
│ │ │ └── product.controller.ts
│ │ ├── api.controller.spec.ts
│ │ └── api.controller.ts
│ ├── app.module.ts
│ ├── decorators
│ │ ├── index.ts
│ │ ├── kb-api-validation-error-response.decorator.ts
│ │ ├── get-all.decorator.ts
│ │ ├── kb-post.decorator.ts
│ │ ├── get-one.decorator.ts
│ │ ├── kb-delete.decorator.ts
│ │ ├── kb-put.decorator.ts
│ │ └── kb-patch.decorator.ts
│ ├── app.controller.ts
│ ├── bootstrap-application.ts
│ ├── app.controller.spec.ts
│ └── swagger.ts
├── tsconfig.build.json
├── test
│ ├── jest-e2e.json
│ └── app.e2e-spec.ts
├── tsconfig.json
├── .eslintrc.js
├── package.json
└── README.md
├── .github
├── FUNDING.yml
└── workflows
│ ├── build.yml
│ └── release.yml
├── commitlint.config.js
├── .vscode
├── extensions.json
├── settings.json
└── tasks.json
├── .all-contributorsrc
├── tools
├── replace-template-string.js
└── get-all-contributors.js
├── .devcontainer
├── docker-compose.yml
├── Dockerfile
└── devcontainer.json
├── .gitignore
├── README.md
├── TEMPLATE_INSTRUCTIONS.md
└── package.json
/client/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.commit-template:
--------------------------------------------------------------------------------
1 | type(scope): subject
2 |
3 | description
--------------------------------------------------------------------------------
/server/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | .eslintrc.js
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom:
2 | - https://paypal.me/thatkookooguy?locale.x=en_US
--------------------------------------------------------------------------------
/client/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kibibit/kibibe/master/client/src/favicon.ico
--------------------------------------------------------------------------------
/server/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src"
4 | }
5 |
--------------------------------------------------------------------------------
/client/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrap } from './bootstrap-application';
2 |
3 | bootstrap()
4 | .then((app) => app.listen(10102));
5 |
--------------------------------------------------------------------------------
/server/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/client/proxy.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "/api/*": {
3 | "target": "http://localhost:10102",
4 | "secure": false,
5 | "logLevel": "debug",
6 | "changeOrigin": true
7 | }
8 | }
--------------------------------------------------------------------------------
/server/src/abstracts/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Automatically generated by barrelsby.
3 | */
4 |
5 | export * from './base.model.abstract';
6 | export * from './base.service.abstract';
7 |
--------------------------------------------------------------------------------
/server/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class AppService {
5 | getHello(): string {
6 | return 'Hello World!';
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/server/src/filters/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Automatically generated by barrelsby.
3 | */
4 |
5 | export * from './kb-not-found-exception.filter';
6 | export * from './kb-validation-exception.filter';
7 |
--------------------------------------------------------------------------------
/server/src/models/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Automatically generated by barrelsby.
3 | */
4 |
5 | export * from './api.model';
6 | export * from './product.model';
7 | export * from './public-error.model';
8 |
--------------------------------------------------------------------------------
/server/src/filters/kb-not-found-exception.filter.spec.ts:
--------------------------------------------------------------------------------
1 | import { KbNotFoundExceptionFilter } from './kb-not-found-exception.filter';
2 |
3 | describe('NotFoundExceptionFilterFilter', () => {
4 | it('should be defined', () => {
5 | expect(new KbNotFoundExceptionFilter()).toBeDefined();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/server/src/api/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Automatically generated by barrelsby.
3 | */
4 |
5 | export * from './api.controller';
6 | export * from './api.module';
7 | export * from './product/product.controller';
8 | export * from './product/product.module';
9 | export * from './product/product.service';
10 |
--------------------------------------------------------------------------------
/server/src/filters/kb-validation-exception.filter.spec.ts:
--------------------------------------------------------------------------------
1 | import { KbValidationExceptionFilter } from './kb-validation-exception.filter';
2 |
3 | describe('KbValidationExceptionFilter', () => {
4 | it('should be defined', () => {
5 | expect(new KbValidationExceptionFilter()).toBeDefined();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/client/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | kibibit Client Side
3 |
4 |
5 |
6 |
Start Writing your app!
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/client/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | const routes: Routes = [];
5 |
6 | @NgModule({
7 | imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
8 | exports: [RouterModule]
9 | })
10 | export class AppRoutingModule { }
11 |
--------------------------------------------------------------------------------
/client/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/client/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/client/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo(): Promise {
5 | return browser.get(browser.baseUrl) as Promise;
6 | }
7 |
8 | getTitleText(): Promise {
9 | return element(by.css('app-root #site-name'))
10 | .getText() as Promise;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/client/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../out-tsc/e2e",
6 | "module": "commonjs",
7 | "target": "es2018",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/server/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import { AppController } from './app.controller';
4 | import { AppService } from './app.service';
5 | import { ApiModule } from './api/api.module';
6 |
7 | @Module({
8 | imports: [ApiModule],
9 | controllers: [AppController],
10 | providers: [AppService]
11 | })
12 | export class AppModule {}
13 |
--------------------------------------------------------------------------------
/server/src/decorators/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Automatically generated by barrelsby.
3 | */
4 |
5 | export * from './get-all.decorator';
6 | export * from './get-one.decorator';
7 | export * from './kb-api-validation-error-response.decorator';
8 | export * from './kb-delete.decorator';
9 | export * from './kb-patch.decorator';
10 | export * from './kb-post.decorator';
11 | export * from './kb-put.decorator';
12 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [ '@commitlint/config-angular' ],
3 | rules: {
4 | 'type-enum': [
5 | 2,
6 | 'always', [
7 | 'build',
8 | 'chore',
9 | 'ci',
10 | 'docs',
11 | 'feat',
12 | 'fix',
13 | 'perf',
14 | 'refactor',
15 | 'revert',
16 | 'style',
17 | 'test'
18 | ]
19 | ]
20 | }
21 | };
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: "Build"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - next
8 | pull_request:
9 | branches:
10 | - master
11 | - next
12 |
13 | jobs:
14 | build:
15 | name: Test build
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v1
19 | - name: Build
20 | run: |
21 | npm install
22 | npm run build
--------------------------------------------------------------------------------
/client/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/client/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/server/src/api/api.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { MongooseModule } from '@nestjs/mongoose';
3 |
4 | import { ApiController } from './api.controller';
5 | import { ProductModule } from './product/product.module';
6 |
7 | @Module({
8 | controllers: [ApiController],
9 | imports: [
10 | MongooseModule.forRoot('mongodb://localhost:27017'),
11 | ProductModule
12 | ]
13 | })
14 | export class ApiModule {}
15 |
--------------------------------------------------------------------------------
/server/src/decorators/kb-api-validation-error-response.decorator.ts:
--------------------------------------------------------------------------------
1 | import { applyDecorators } from '@nestjs/common';
2 | import { ApiResponse } from '@nestjs/swagger';
3 |
4 | import { PublicError } from '@kb-models';
5 |
6 | export const KbApiValidateErrorResponse = () => {
7 | return applyDecorators(
8 | ApiResponse({
9 | description: 'Some validation error as accured on the given model',
10 | status: 405,
11 | type: PublicError
12 | })
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "rbbit.typescript-hero",
4 | "coenraads.bracket-pair-colorizer",
5 | "wix.vscode-import-cost",
6 | "orta.vscode-jest",
7 | "dbaeumer.vscode-eslint",
8 | "actboy168.tasks",
9 | "johnpapa.vscode-peacock",
10 | "angular.ng-template",
11 | "abhijoybasak.nestjs-files",
12 | "eamodio.gitlens",
13 | "codeandstuff.package-json-upgrade",
14 | "mongodb.mongodb-vscode",
15 | "ms-azuretools.vscode-docker",
16 | "jock.svg"
17 | ]
18 | }
--------------------------------------------------------------------------------
/client/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { webConsolelogo } from '@kibibit/consologo';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | templateUrl: './app.component.html',
7 | styleUrls: ['./app.component.scss']
8 | })
9 | export class AppComponent {
10 | title = 'kibibit client';
11 |
12 | constructor() {
13 | webConsolelogo('kibibit client template', [
14 | 'kibibit server-client template',
15 | 'change this up in app.component.ts'
16 | ]);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/server/src/models/api.model.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class ApiInfo {
4 | @ApiProperty()
5 | name: string;
6 |
7 | @ApiProperty()
8 | description: string;
9 |
10 | @ApiProperty()
11 | version: string;
12 |
13 | @ApiProperty()
14 | license: string;
15 |
16 | @ApiProperty()
17 | repository: string;
18 |
19 | @ApiProperty()
20 | author: string;
21 |
22 | @ApiProperty()
23 | bugs: string;
24 |
25 | constructor(partial: Partial = {}) {
26 | Object.assign(this, partial);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/src/api/product/product.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { MongooseModule } from '@nestjs/mongoose';
3 |
4 | import { Product } from '../../models/product.model';
5 | import { ProductController } from './product.controller';
6 | import { ProductService } from './product.service';
7 |
8 | @Module({
9 | imports: [
10 | MongooseModule.forFeature([
11 | { name: Product.modelName, schema: Product.schema }
12 | ])
13 | ],
14 | providers: [ProductService],
15 | controllers: [ProductController]
16 | })
17 | export class ProductModule {}
18 |
--------------------------------------------------------------------------------
/server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | },
9 | "moduleNameMapper": {
10 | "^@kb-server$": "/../src",
11 | "^@kb-models$": "/../src/../src/models/index",
12 | "^@kb-abstracts$": "/../src/abstracts/index",
13 | "^@kb-decorators$": "/../src/decorators/index",
14 | "^@kb-filters$": "/../src/filters/index",
15 | "^@kb-api$": "/../src/api/index"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/src/decorators/get-all.decorator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | applyDecorators,
3 | ClassSerializerInterceptor,
4 | Get,
5 | UseInterceptors
6 | } from '@nestjs/common';
7 | import { ApiOkResponse, ApiOperation } from '@nestjs/swagger';
8 |
9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
10 | export function GetAll(model: any, path?: string | string[]) {
11 | return applyDecorators(
12 | Get(path),
13 | ApiOperation({ summary: `Get all ${ model.name }` }),
14 | ApiOkResponse({ description: `Return a list of all ${ model.name }s` }),
15 | UseInterceptors(ClassSerializerInterceptor)
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "downlevelIteration": true,
10 | "experimentalDecorators": true,
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "module": "es2020",
15 | "lib": [
16 | "es2018",
17 | "dom"
18 | ]
19 | },
20 | "angularCompilerOptions": {
21 | "disableTypeScriptVersionCheck": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/src/models/public-error.model.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@nestjs/common';
2 | import { ApiProperty } from '@nestjs/swagger';
3 |
4 | export class PublicError {
5 | @ApiProperty()
6 | statusCode: HttpStatus;
7 |
8 | // toISOString formatted Date
9 | @ApiProperty()
10 | timestamp: string;
11 |
12 | @ApiProperty()
13 | path: string;
14 |
15 | @ApiProperty()
16 | name: string;
17 |
18 | @ApiProperty({
19 | oneOf: [{ type: 'string' }, { type: '[string]' }]
20 | })
21 | error: string | string[];
22 |
23 | constructor(partial: Partial = {}) {
24 | Object.assign(this, partial);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/src/api/api.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 |
3 | import { ApiController } from './api.controller';
4 |
5 | describe('ApiController', () => {
6 | let controller: ApiController;
7 |
8 | beforeEach(async () => {
9 | const module: TestingModule = await Test.createTestingModule({
10 | controllers: [ApiController]
11 | }).compile();
12 |
13 | controller = module.get(ApiController);
14 | });
15 |
16 | it('should be defined', () => {
17 | expect(controller).toBeDefined();
18 | });
19 |
20 | it('should return "Hello World!"', () => {
21 | expect(controller.getAPI()).toBe('API');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/server/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Res } from '@nestjs/common';
2 | import { ApiOkResponse, ApiOperation } from '@nestjs/swagger';
3 | import { Response } from 'express';
4 | import { join } from 'path';
5 |
6 | import { AppService } from './app.service';
7 |
8 | @Controller()
9 | export class AppController {
10 | appRoot = join(__dirname, '../../');
11 | constructor(private readonly appService: AppService) {}
12 |
13 | @Get()
14 | @ApiOperation({ summary: 'Get Web Client (HTML)' })
15 | @ApiOkResponse({
16 | description: 'Returns the Web Client\'s HTML File'
17 | })
18 | sendWebClient(@Res() res: Response): void {
19 | res.sendFile(join(this.appRoot, 'dist/client/index.html'));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/client/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { browser, logging } from 'protractor';
2 |
3 | import { AppPage } from './app.po';
4 |
5 | describe('workspace-project App', () => {
6 | let page: AppPage;
7 |
8 | beforeEach(() => {
9 | page = new AppPage();
10 | });
11 |
12 | it('should display welcome message', () => {
13 | page.navigateTo();
14 | expect(page.getTitleText()).toEqual('kibibit Client Side');
15 | });
16 |
17 | afterEach(async () => {
18 | // Assert that there are no errors emitted from the browser
19 | const logs = await browser.manage().logs().get(logging.Type.BROWSER);
20 | expect(logs).not.toContain(jasmine.objectContaining({
21 | level: logging.Level.SEVERE
22 | } as logging.Entry));
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "es2017",
10 | "sourceMap": true,
11 | "outDir": "../dist/server",
12 | "baseUrl": "./",
13 | "paths": {
14 | "@kb-server": [ "src/*" ],
15 | "@kb-models": [ "src/models/index" ],
16 | "@kb-abstracts": [ "src/abstracts/index" ],
17 | "@kb-decorators": [ "src/decorators/index" ],
18 | "@kb-filters": [ "src/filters/index" ],
19 | "@kb-api": [ "src/api/index" ]
20 |
21 | },
22 | "incremental": true,
23 | "skipLibCheck": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - dummy
6 | # - master
7 | # - next
8 | jobs:
9 | release:
10 | name: Release
11 | runs-on: ubuntu-18.04
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 | - name: Setup Node.js
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: 12
21 | - name: Install dependencies
22 | run: |
23 | npm ci
24 | npm install
25 | npm run build
26 | - name: Release
27 | env:
28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
30 | run: npm run semantic-release
--------------------------------------------------------------------------------
/client/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 |
5 | import {
6 | AngularMaterialModule
7 | } from './angular-material/angular-material.module';
8 | import { AppRoutingModule } from './app-routing.module';
9 | import { AppComponent } from './app.component';
10 |
11 | @NgModule({
12 | declarations: [
13 | AppComponent
14 | ],
15 | imports: [
16 | BrowserModule,
17 | AppRoutingModule,
18 | BrowserAnimationsModule,
19 | AngularMaterialModule
20 | ],
21 | providers: [],
22 | bootstrap: [AppComponent],
23 | schemas: [CUSTOM_ELEMENTS_SCHEMA]
24 | })
25 | export class AppModule { }
26 |
--------------------------------------------------------------------------------
/server/src/decorators/kb-post.decorator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | applyDecorators,
3 | ClassSerializerInterceptor,
4 | Post,
5 | UseInterceptors
6 | } from '@nestjs/common';
7 | import { ApiCreatedResponse, ApiOperation } from '@nestjs/swagger';
8 |
9 | import { KbApiValidateErrorResponse } from '@kb-decorators';
10 |
11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
12 | export function KbPost(type: any, path?: string | string[]) {
13 | return applyDecorators(
14 | Post(path),
15 | ApiOperation({ summary: `Create a new ${ type.name }` }),
16 | ApiCreatedResponse({
17 | description: `The ${ type.name } has been successfully created.`,
18 | type
19 | }),
20 | KbApiValidateErrorResponse(),
21 | UseInterceptors(ClassSerializerInterceptor)
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/server/src/models/product.model.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2 | import { prop as PersistInDb } from '@typegoose/typegoose';
3 | import { Exclude, Expose } from 'class-transformer';
4 | import { IsNotEmpty } from 'class-validator';
5 |
6 | import { BaseModel } from '../abstracts/base.model.abstract';
7 |
8 | @Exclude()
9 | export class Product extends BaseModel {
10 |
11 | @Expose()
12 | @IsNotEmpty()
13 | @ApiProperty()
14 | @PersistInDb()
15 | name: string;
16 |
17 | @Expose()
18 | @ApiPropertyOptional()
19 | @PersistInDb()
20 | description: string;
21 |
22 | @Expose()
23 | @IsNotEmpty()
24 | @ApiProperty()
25 | @PersistInDb()
26 | price: number;
27 |
28 | constructor(partial: Partial = {}) {
29 | super();
30 | Object.assign(this, partial);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Client
6 |
7 |
11 |
16 |
20 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/server/src/api/product/product.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { InjectModel } from '@nestjs/mongoose';
3 | import { ReturnModelType } from '@typegoose/typegoose';
4 |
5 | import { BaseService } from '../../abstracts/base.service.abstract';
6 | import { Product } from '../../models/product.model';
7 |
8 | @Injectable()
9 | export class ProductService extends BaseService {
10 | constructor(
11 | @InjectModel(Product.modelName)
12 | private readonly productModel: ReturnModelType,
13 | ) {
14 | super(productModel, Product);
15 | }
16 |
17 | async findByName(name: string): Promise {
18 | const dbProduct = await this.findOne({ name }).exec();
19 |
20 | if (!dbProduct) {
21 | return;
22 | }
23 |
24 | return new Product(dbProduct.toObject());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/src/decorators/get-one.decorator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | applyDecorators,
3 | ClassSerializerInterceptor,
4 | Get,
5 | UseInterceptors
6 | } from '@nestjs/common';
7 | import {
8 | ApiBadRequestResponse,
9 | ApiNotFoundResponse,
10 | ApiOkResponse,
11 | ApiOperation
12 | } from '@nestjs/swagger';
13 |
14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
15 | export function GetOne(type: any, path?: string | string[]) {
16 | return applyDecorators(
17 | Get(path),
18 | ApiOperation({ summary: `Get an existing ${ type.name }` }),
19 | ApiOkResponse({ description: `Return a single ${ type.name }`, type }),
20 | ApiNotFoundResponse({ description: `${ type.name } not found` }),
21 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }),
22 | UseInterceptors(ClassSerializerInterceptor)
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads
2 | // recursively all the .spec and framework files
3 | import 'zone.js/dist/zone-testing';
4 |
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 |
12 | declare const require: {
13 | context(path: string, deep?: boolean, filter?: RegExp): {
14 | keys(): string[];
15 | (id: string): T;
16 | };
17 | };
18 |
19 | // First, initialize the Angular testing environment.
20 | getTestBed().initTestEnvironment(
21 | BrowserDynamicTestingModule,
22 | platformBrowserDynamicTesting()
23 | );
24 | // Then we find all the tests.
25 | const context = require.context('./', true, /\.spec\.ts$/);
26 | // And load the modules.
27 | context.keys().map(context);
28 |
--------------------------------------------------------------------------------
/server/src/filters/kb-not-found-exception.filter.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ArgumentsHost,
3 | Catch,
4 | ExceptionFilter,
5 | NotFoundException
6 | } from '@nestjs/common';
7 | import { resolve } from 'path';
8 |
9 | @Catch(NotFoundException)
10 | export class KbNotFoundExceptionFilter implements ExceptionFilter {
11 | catch(exception: NotFoundException, host: ArgumentsHost) {
12 | const ctx = host.switchToHttp();
13 | const response = ctx.getResponse();
14 | const request = ctx.getRequest();
15 | const path: string = request.path;
16 |
17 | if (path.startsWith('/api/')) {
18 | response.status(exception.getStatus()).json({
19 | statusCode: exception.getStatus(),
20 | name: exception.name,
21 | error: exception.message
22 | });
23 |
24 | return;
25 | }
26 |
27 | response.sendFile(resolve('./dist/client/index.html'));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/src/decorators/kb-delete.decorator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | applyDecorators,
3 | ClassSerializerInterceptor,
4 | Delete,
5 | UseInterceptors
6 | } from '@nestjs/common';
7 | import {
8 | ApiBadRequestResponse,
9 | ApiNotFoundResponse,
10 | ApiOkResponse,
11 | ApiOperation
12 | } from '@nestjs/swagger';
13 |
14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
15 | export function KbDelete(type: any, path?: string | string[]) {
16 | return applyDecorators(
17 | Delete(path),
18 | ApiOperation({
19 | summary: `Delete an existing ${ type.name }`
20 | }),
21 | ApiOkResponse({ type: type, description: `${ type.name } deleted` }),
22 | ApiNotFoundResponse({
23 | description: `${ type.name } not found`
24 | }),
25 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }),
26 | UseInterceptors(ClassSerializerInterceptor)
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/client/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
19 |
--------------------------------------------------------------------------------
/server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test, TestingModule } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 |
5 | import { AppModule } from './../src/app.module';
6 |
7 | describe('AppController (e2e)', () => {
8 | let app: INestApplication;
9 |
10 | beforeEach(async () => {
11 | const moduleFixture: TestingModule = await Test.createTestingModule({
12 | imports: [AppModule]
13 | }).compile();
14 |
15 | app = moduleFixture.createNestApplication();
16 | await app.init();
17 | });
18 |
19 | test('/api (GET)', () => {
20 | return request(app.getHttpServer())
21 | .get('/api')
22 | .expect(200)
23 | .expect('Hello World!');
24 | });
25 |
26 | test('/ (GET)', () => {
27 | return request(app.getHttpServer())
28 | .get('/')
29 | .expect(200)
30 | .expect('Content-Type', /html/);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/server/src/bootstrap-application.ts:
--------------------------------------------------------------------------------
1 | import { terminalConsoleLogo } from '@kibibit/consologo';
2 | import { ValidationPipe } from '@nestjs/common';
3 | import { NestFactory } from '@nestjs/core';
4 | import { NestExpressApplication } from '@nestjs/platform-express';
5 | import { join } from 'path';
6 |
7 | import { KbNotFoundExceptionFilter } from '@kb-filters';
8 |
9 | import { AppModule } from './app.module';
10 | import { Swagger } from './swagger';
11 |
12 | export async function bootstrap(): Promise {
13 | terminalConsoleLogo('kibibit server template', [
14 | 'change this in server/src/main.ts'
15 | ]);
16 | const app = await NestFactory.create(AppModule);
17 | app.useGlobalFilters(new KbNotFoundExceptionFilter());
18 | app.useGlobalPipes(new ValidationPipe());
19 | app.useStaticAssets(join(__dirname, '../client'));
20 |
21 | await Swagger.addSwagger(app);
22 |
23 | return app;
24 | }
25 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "kb-server-client-template",
3 | "projectOwner": "Kibibit",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "badgeTemplate": "
-orange.svg?style=flat-square\" alt=\"All Contributors\">",
10 | "imageSize": 100,
11 | "commit": false,
12 | "commitConvention": "angular",
13 | "contributors": [
14 | {
15 | "login": "Thatkookooguy",
16 | "name": "Neil Kalman",
17 | "avatar_url": "https://avatars3.githubusercontent.com/u/10427304?v=4",
18 | "profile": "http://thatkookooguy.kibibit.io/",
19 | "contributions": [
20 | "code",
21 | "doc",
22 | "design",
23 | "maintenance",
24 | "infra",
25 | "test"
26 | ]
27 | }
28 | ],
29 | "contributorsPerLine": 7,
30 | "skipCi": true
31 | }
32 |
--------------------------------------------------------------------------------
/server/src/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { mockResponse } from 'jest-mock-req-res';
3 |
4 | import { AppController } from './app.controller';
5 | import { AppService } from './app.service';
6 |
7 | describe('AppController', () => {
8 | let appController: AppController;
9 |
10 | beforeEach(async () => {
11 | const app: TestingModule = await Test.createTestingModule({
12 | controllers: [AppController],
13 | providers: [AppService]
14 | }).compile();
15 |
16 | appController = app.get(AppController);
17 | });
18 |
19 | describe('root', () => {
20 | it('should return an HTML page', async () => {
21 | const mocRes = mockResponse();
22 | appController.sendWebClient(mocRes);
23 | expect(mocRes.sendFile.mock.calls.length).toBe(1);
24 | const param = mocRes.sendFile.mock.calls[0][0] as string;
25 | expect(param.endsWith('dist/client/index.html')).toBeTruthy();
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/server/src/decorators/kb-put.decorator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | applyDecorators,
3 | ClassSerializerInterceptor,
4 | Put,
5 | UseInterceptors
6 | } from '@nestjs/common';
7 | import {
8 | ApiBadRequestResponse,
9 | ApiNotFoundResponse,
10 | ApiOkResponse,
11 | ApiOperation
12 | } from '@nestjs/swagger';
13 |
14 | import { KbApiValidateErrorResponse } from '@kb-decorators';
15 |
16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 | export function KbPut(type: any, path?: string | string[]) {
18 | return applyDecorators(
19 | Put(path),
20 | ApiOperation({
21 | summary: `Update an existing ${ type.name }`,
22 | description: `Expects a full ${ type.name }`
23 | }),
24 | ApiOkResponse({ type: type, description: `${ type.name } updated` }),
25 | ApiNotFoundResponse({
26 | description: `${ type.name } not found`
27 | }),
28 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }),
29 | KbApiValidateErrorResponse(),
30 | UseInterceptors(ClassSerializerInterceptor)
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/server/src/decorators/kb-patch.decorator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | applyDecorators,
3 | ClassSerializerInterceptor,
4 | Patch,
5 | UseInterceptors
6 | } from '@nestjs/common';
7 | import {
8 | ApiBadRequestResponse,
9 | ApiNotFoundResponse,
10 | ApiOkResponse,
11 | ApiOperation
12 | } from '@nestjs/swagger';
13 |
14 | import { KbApiValidateErrorResponse } from '@kb-decorators';
15 |
16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 | export function KbPatch(type: any, path?: string | string[]) {
18 | return applyDecorators(
19 | Patch(path),
20 | ApiOperation({
21 | summary: `Update an existing ${ type.name }`,
22 | description: `Expects a partial ${ type.name }`
23 | }),
24 | ApiOkResponse({ type: type, description: `${ type.name } updated` }),
25 | ApiNotFoundResponse({
26 | description: `${ type.name } not found`
27 | }),
28 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }),
29 | KbApiValidateErrorResponse(),
30 | UseInterceptors(ClassSerializerInterceptor)
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/server/src/filters/kb-validation-exception.filter.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ArgumentsHost,
3 | BadRequestException,
4 | Catch,
5 | ExceptionFilter,
6 | HttpStatus
7 | } from '@nestjs/common';
8 |
9 | import { PublicError } from '@kb-models';
10 |
11 | @Catch(BadRequestException)
12 | export class KbValidationExceptionFilter implements ExceptionFilter {
13 | catch(exception: BadRequestException, host: ArgumentsHost) {
14 | console.error(exception);
15 | const ctx = host.switchToHttp();
16 | const response = ctx.getResponse();
17 | const request = ctx.getRequest();
18 |
19 | response
20 | .status(HttpStatus.METHOD_NOT_ALLOWED)
21 | // you can manipulate the response here
22 | .json(new PublicError({
23 | statusCode: HttpStatus.METHOD_NOT_ALLOWED,
24 | timestamp: new Date().toISOString(),
25 | path: request.url,
26 | name: 'BadRequestException',
27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
28 | error: (exception.getResponse() as any).message as string[]
29 | }));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Client
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/client/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Protractor configuration file, see link for more information
3 | // https://github.com/angular/protractor/blob/master/lib/config.ts
4 |
5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
6 |
7 | /**
8 | * @type { import("protractor").Config }
9 | */
10 | exports.config = {
11 | allScriptsTimeout: 11000,
12 | specs: [
13 | './src/**/*.e2e-spec.ts'
14 | ],
15 | capabilities: {
16 | browserName: 'chrome',
17 | chromeOptions: {
18 | args: [ '--headless', '--disable-gpu', '--window-size=800,600', '--log-level=3' ]
19 | }
20 | },
21 | directConnect: true,
22 | baseUrl: 'http://localhost:10101/',
23 | framework: 'jasmine',
24 | jasmineNodeOpts: {
25 | showColors: true,
26 | defaultTimeoutInterval: 30000,
27 | print: function() {}
28 | },
29 | onPrepare() {
30 | require('ts-node').register({
31 | project: require('path').join(__dirname, './tsconfig.json')
32 | });
33 | jasmine.getEnv().addReporter(new SpecReporter({
34 | spec: {
35 | displayStacktrace: StacktraceOption.PRETTY
36 | }
37 | }));
38 | }
39 | };
--------------------------------------------------------------------------------
/server/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/eslint-recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | ],
12 | ignorePatterns: [ '.eslintrc.js' ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | rules: {
19 | 'eol-last': [ 2, 'windows' ],
20 | 'comma-dangle': [ 'error', 'never' ],
21 | 'max-len': [ 'error', { 'code': 80, "ignoreComments": true } ],
22 | 'quotes': ["error", "single"],
23 | '@typescript-eslint/no-empty-interface': 'error',
24 | '@typescript-eslint/member-delimiter-style': 'error',
25 | '@typescript-eslint/explicit-function-return-type': 'off',
26 | '@typescript-eslint/explicit-module-boundary-types': 'off',
27 | '@typescript-eslint/naming-convention': [
28 | "error",
29 | {
30 | "selector": "interface",
31 | "format": ["PascalCase"],
32 | "custom": {
33 | "regex": "^I[A-Z]",
34 | "match": true
35 | }
36 | }
37 | ]
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/client/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 |
4 | import {
5 | AngularMaterialModule
6 | } from './angular-material/angular-material.module';
7 | import { AppComponent } from './app.component';
8 |
9 | describe('AppComponent', () => {
10 | beforeEach(async () => {
11 | await TestBed.configureTestingModule({
12 | imports: [
13 | RouterTestingModule,
14 | AngularMaterialModule
15 | ],
16 | declarations: [
17 | AppComponent
18 | ]
19 | }).compileComponents();
20 | });
21 |
22 | it('should create the app', () => {
23 | const fixture = TestBed.createComponent(AppComponent);
24 | const app = fixture.componentInstance;
25 | expect(app).toBeTruthy();
26 | });
27 |
28 | it(`should have as title 'kibibit client'`, () => {
29 | const fixture = TestBed.createComponent(AppComponent);
30 | const app = fixture.componentInstance;
31 | expect(app.title).toEqual('kibibit client');
32 | });
33 |
34 | it('should render title', () => {
35 | const fixture = TestBed.createComponent(AppComponent);
36 | fixture.detectChanges();
37 | const compiled = fixture.nativeElement;
38 | expect(compiled.querySelector('#site-name').textContent)
39 | .toContain('kibibit Client Side');
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/tools/replace-template-string.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const replace = require('replace-in-file');
3 |
4 | const projectNameArgs = process.argv.slice(2);
5 | const projectName = projectNameArgs[projectNameArgs.length - 1];
6 |
7 | if (!projectName) {
8 | throw new Error('must pass a project name');
9 | } else {
10 | console.log(projectName);
11 | // return;
12 | }
13 |
14 | const readmeFile = {
15 | files: './README.md',
16 | from: /kb-server-client-template/g,
17 | to: projectName,
18 | };
19 |
20 | const contributorsFile = {
21 | files: './.all-contributorsrc',
22 | from: /kb-server-client-template/g,
23 | to: projectName,
24 | };
25 |
26 | const packageFile = {
27 | files: './package.json',
28 | from: /kb-server-client-template/g,
29 | to: projectName,
30 | };
31 |
32 | const packageLockFile = {
33 | files: './package-lock.json',
34 | from: /kb-server-client-template/g,
35 | to: projectName,
36 | };
37 |
38 |
39 |
40 | (async () => {
41 | try {
42 | let results = [];
43 | results.push(await replace(readmeFile));
44 | results.push(await replace(packageFile));
45 | results.push(await replace(packageLockFile));
46 | results.push(await replace(contributorsFile));
47 | console.log('Replacement results:', results);
48 | }
49 | catch (error) {
50 | console.error('Error occurred:', error);
51 | }
52 | })();
53 |
--------------------------------------------------------------------------------
/server/src/api/api.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Logger } from '@nestjs/common';
2 | import { ApiOkResponse, ApiOperation } from '@nestjs/swagger';
3 | import { readJSON } from 'fs-extra';
4 | import { chain } from 'lodash';
5 | import { join } from 'path';
6 |
7 | import { ApiInfo } from '@kb-models';
8 |
9 | @Controller('api')
10 | export class ApiController {
11 | appRoot = join(__dirname, '../../../');
12 |
13 | private readonly logger = new Logger(ApiController.name);
14 |
15 | @Get()
16 | @ApiOperation({ summary: 'Get API Information' })
17 | @ApiOkResponse({
18 | description: 'Returns API info as a JSON',
19 | type: ApiInfo
20 | })
21 | async getAPI() {
22 | const packageInfo = await readJSON(join(this.appRoot, './package.json'));
23 | const details = new ApiInfo(
24 | chain(packageInfo)
25 | .pick([
26 | 'name',
27 | 'description',
28 | 'version',
29 | 'license',
30 | 'repository',
31 | 'author',
32 | 'bugs'
33 | ])
34 | .mapValues((val) => val.url ? val.url : val)
35 | .value()
36 | );
37 | this.logger.log('Api information requested');
38 | return details;
39 | }
40 |
41 | @Get('/nana')
42 | @ApiOperation({
43 | deprecated: true
44 | })
45 | async deprecationTest() {
46 | return new Promise((resolve) => setTimeout(() => resolve('hello'), 60000));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/src/abstracts/base.model.abstract.ts:
--------------------------------------------------------------------------------
1 | import { buildSchema, prop as PersistInDb } from '@typegoose/typegoose';
2 | import { classToPlain, Exclude, Expose } from 'class-transformer';
3 | import { Schema } from 'mongoose';
4 |
5 | @Exclude()
6 | export abstract class BaseModel {
7 | @PersistInDb()
8 | createdDate?: Date; // provided by timestamps
9 | @Expose()
10 | @PersistInDb()
11 | updatedDate?: Date; // provided by timestamps
12 |
13 | // @Expose({ name: 'id' })
14 | // @Transform(({ value }) => value && value.toString())
15 | // tslint:disable-next-line: variable-name
16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 | _id?: any;
18 |
19 | id?: string; // is actually model._id getter
20 |
21 | // tslint:disable-next-line: variable-name
22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
23 | _v?: any;
24 |
25 | // add more to a base model if you want.
26 |
27 | toJSON() {
28 | return classToPlain(this);
29 | }
30 |
31 | toString() {
32 | return JSON.stringify(this.toJSON());
33 | }
34 |
35 | static get schema(): Schema {
36 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
37 | return buildSchema(this as any, {
38 | timestamps: true,
39 | toJSON: {
40 | getters: true,
41 | virtuals: true
42 | }
43 | });
44 | }
45 |
46 | static get modelName(): string {
47 | return this.name;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/client/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json",
14 | "e2e/tsconfig.json"
15 | ],
16 | "tsconfigRootDir": __dirname,
17 | "createDefaultProgram": true
18 | },
19 | "extends": [
20 | "plugin:@angular-eslint/ng-cli-compat",
21 | "plugin:@angular-eslint/ng-cli-compat--formatting-add-on",
22 | "plugin:@angular-eslint/template/process-inline-templates"
23 | ],
24 | "rules": {
25 | 'eol-last': [ 2, 'windows' ],
26 | 'comma-dangle': [ 'error', 'never' ],
27 | 'max-len': [ 'error', { 'code': 80, "ignoreComments": true } ],
28 | "@angular-eslint/component-selector": [
29 | "error",
30 | {
31 | "type": "element",
32 | "prefix": "app",
33 | "style": "kebab-case"
34 | }
35 | ],
36 | "@angular-eslint/directive-selector": [
37 | "error",
38 | {
39 | "type": "attribute",
40 | "prefix": "app",
41 | "style": "camelCase"
42 | }
43 | ]
44 | }
45 | },
46 | {
47 | "files": [
48 | "*.html"
49 | ],
50 | "extends": [
51 | "plugin:@angular-eslint/template/recommended"
52 | ],
53 | "rules": {}
54 | }
55 | ]
56 | };
57 |
--------------------------------------------------------------------------------
/.devcontainer/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | app:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | args:
9 | # [Choice] Node.js version: 14, 12, 10
10 | VARIANT: 14
11 | # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
12 | USER_UID: 1000
13 | USER_GID: 1000
14 |
15 | volumes:
16 | - ..:/workspace:cached
17 |
18 | # Overrides default command so things don't shut down after the process ends.
19 | command: sleep infinity
20 |
21 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
22 | network_mode: service:db
23 |
24 | # Uncomment the next line to use a non-root user for all processes.
25 | # user: node
26 |
27 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
28 | # (Adding the "ports" property to this file will not forward from a Codespace.)
29 |
30 | db:
31 | image: mongo:latest
32 | restart: unless-stopped
33 | volumes:
34 | - mongodb-data:/data/db
35 |
36 | # Uncomment to change startup options
37 | # environment:
38 | # MONGO_INITDB_ROOT_USERNAME: root
39 | # MONGO_INITDB_ROOT_PASSWORD: example
40 | # MONGO_INITDB_DATABASE: your-database-here
41 |
42 | # Add "forwardPorts": ["27017"] to **devcontainer.json** to forward MongoDB locally.
43 | # (Adding the "ports" property to this file will not forward from a Codespace.)
44 |
45 | volumes:
46 | mongodb-data:
--------------------------------------------------------------------------------
/client/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma'),
14 | require('karma-spec-reporter')
15 | ],
16 | client: {
17 | captureConsole: false,
18 | clearContext: false // leave Jasmine Spec Runner output visible in browser
19 | },
20 | preprocessors: {
21 | 'src/**/*!(test|spec).ts': ['coverage']
22 | },
23 | // coverageIstanbulReporter: {
24 | // dir: require('path').join(__dirname, '../coverage/client'),
25 | // reports: ['html', 'lcovonly', 'text-summary'],
26 | // fixWebpackSourcePaths: true
27 | // },
28 | coverageReporter: {
29 | // type : 'html',
30 | dir : '../coverage/client',
31 | reporters: [
32 | { type: 'lcov', subdir: 'report-html' },
33 | { type: 'html', subdir: 'report-lcov' }
34 | ],
35 | fixWebpackSourcePaths: true
36 | },
37 |
38 | reporters: ['spec', 'kjhtml'],
39 | port: 9876,
40 | colors: true,
41 | logLevel: config.LOG_INFO,
42 | autoWatch: true,
43 | browsers: ['Chrome'],
44 | singleRun: false,
45 | restartOnFileChange: true
46 | });
47 | };
48 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "peacock.color": "#3334f4",
3 | "editor.tabSize": 2,
4 | "editor.insertSpaces": true,
5 | "editor.detectIndentation": false,
6 | "editor.rulers": [80],
7 | "editor.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace",
8 | "editor.matchBrackets": "always",
9 | // Terminal
10 | "terminal.integrated.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace",
11 | "terminal.integrated.fontSize": 14,
12 | // Workbench
13 | "workbench.colorTheme": "Andromeda",
14 | "workbench.editor.showIcons": true,
15 | "workbench.iconTheme": "vs-seti",
16 | // Bracket Pair Colorizer
17 | "bracketPairColorizer.colorMode": "Consecutive",
18 | "bracketPairColorizer.forceUniqueOpeningColor": true,
19 | "bracketPairColorizer.showBracketsInGutter": true,
20 | "window.title": "${activeEditorShort}${separator}${rootName} [kibibit]",
21 | "typescriptHero.imports.stringQuoteStyle": "'",
22 | "typescriptHero.imports.grouping": [
23 | "Plains",
24 | "Modules",
25 | "/^@kb-/",
26 | "Workspace"
27 |
28 | ],
29 | "typescriptHero.imports.organizeOnSave": true,
30 | "typescriptHero.imports.multiLineTrailingComma": false,
31 | "typescriptHero.imports.multiLineWrapThreshold": 80,
32 | "typescriptHero.imports.insertSpaceBeforeAndAfterImportBraces": true,
33 | "typescriptHero.imports.insertSemicolons": true,
34 | "editor.codeActionsOnSave": {
35 | "source.fixAll.eslint": true,
36 | },
37 | "eslint.format.enable": true,
38 | "vsicons.presets.angular": true,
39 | "eslint.workingDirectories": [
40 | "client/",
41 | "server/"
42 | ],
43 | "svg.preview.background": "black"
44 | }
--------------------------------------------------------------------------------
/tools/get-all-contributors.js:
--------------------------------------------------------------------------------
1 | const gitlog = require('gitlog').default;
2 | const { join } = require('path');
3 | const githubUsername = require('github-username');
4 | const shell = require('shelljs');
5 | const { forEach, chain } = require('lodash');
6 | const { readJson } = require('fs-extra');
7 |
8 |
9 | (async () => {
10 | const allContributorsConfig = await readJson(join(__dirname, '..', '/.all-contributorsrc'));
11 | const data = gitlog({
12 | repo: join(__dirname, '..'),
13 | fields: ["authorName", "authorEmail", "authorDate"]
14 | });
15 |
16 | let result = {};
17 |
18 | for (const commit of data) {
19 | const isCode = commit.files.find((item) => item.startsWith('server/src') || item.startsWith('client/src'));
20 | const isInfra = commit.files.find((item) => item.startsWith('.github/') || item.startsWith('tools/'));
21 | const isTests = commit.files.find((item) => item.endsWith('.spec.ts'));
22 | let types = [];
23 | if (isCode) { types.push('code'); }
24 | if (isInfra) { types.push('infra'); }
25 | if (isTests) { types.push('test'); }
26 |
27 | if (!result[commit.authorEmail]) {
28 | const githubLogin = await githubUsername(commit.authorEmail);
29 | result[commit.authorEmail] = {
30 | githubLogin,
31 | types
32 | };
33 | } else {
34 | result[commit.authorEmail].types = result[commit.authorEmail].types.concat(types);
35 | }
36 | }
37 |
38 | forEach(result, (person, email) => {
39 | const existing = chain(allContributorsConfig.contributors)
40 | .find((item) => item.login === person.githubLogin)
41 | .get('contributions', [])
42 | .value();
43 | const types = chain(person.types.concat(existing)).uniq().sortBy().value();
44 | const command = `npm run contributors:add ${ person.githubLogin } ${ types.join(',') }`;
45 | console.log(command);
46 | shell.exec(command, { cwd: __dirname });
47 | })
48 | })();
--------------------------------------------------------------------------------
/client/src/styles.scss:
--------------------------------------------------------------------------------
1 |
2 | // Custom Theming for Angular Material
3 | // For more information: https://material.angular.io/guide/theming
4 | @import '~@angular/material/theming';
5 | // Plus imports for other components in your app.
6 |
7 | // Define a custom typography config that overrides the font-family as well as the
8 | // `headlines` and `body-1` levels.
9 | $custom-typography: mat-typography-config(
10 | $font-family: "'Comfortaa', cursive",
11 | $headline: mat-typography-level(32px, 48px, 700),
12 | $body-1: mat-typography-level(16px, 24px, 500)
13 | );
14 |
15 |
16 | // Include the common styles for Angular Material. We include this here so that you only
17 | // have to load a single css file for Angular Material in your app.
18 | // Be sure that you only ever include this mixin once!
19 | @include mat-core($custom-typography);
20 |
21 | // Define the palettes for your theme using the Material Design palettes available in palette.scss
22 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker
23 | // hue. Available color palettes: https://material.io/design/color/
24 | $client-primary: mat-palette($mat-indigo);
25 | $client-accent: mat-palette($mat-pink, A200, A100, A400);
26 |
27 | // The warn palette is optional (defaults to red).
28 | $client-warn: mat-palette($mat-red);
29 |
30 | // Create the theme object. A theme consists of configurations for individual
31 | // theming systems such as "color" or "typography".
32 | $client-theme: mat-dark-theme((
33 | color: (
34 | primary: $client-primary,
35 | accent: $client-accent,
36 | warn: $client-warn,
37 | )
38 | ));
39 |
40 | // Include theme styles for core and each component used in your app.
41 | // Alternatively, you can import and @include the theme mixins for each component
42 | // that you are using.
43 | @include angular-material-theme($client-theme);
44 |
45 | /* You can add global styles to this file, and also import other style files */
46 |
47 | html, body { height: 100%; }
48 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; background: #212121; }
49 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/typescript-node/.devcontainer/base.Dockerfile
2 |
3 | # [Choice] Node.js version: 14, 12, 10
4 | ARG VARIANT="14-buster"
5 | FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
6 |
7 | # Install MongoDB command line tools
8 | ARG MONGO_TOOLS_VERSION=4.2
9 | RUN curl -sSL "https://www.mongodb.org/static/pgp/server-${MONGO_TOOLS_VERSION}.asc" | (OUT=$(apt-key add - 2>&1) || echo $OUT) \
10 | && echo "deb http://repo.mongodb.org/apt/debian $(lsb_release -cs)/mongodb-org/${MONGO_TOOLS_VERSION} main" | tee /etc/apt/sources.list.d/mongodb-org-${MONGO_TOOLS_VERSION}.list \
11 | && apt-get update && export DEBIAN_FRONTEND=noninteractive \
12 | && apt-get install -y mongodb-org-tools mongodb-org-shell \
13 | && apt-get clean -y && rm -rf /var/lib/apt/lists/*
14 |
15 | # Update args in docker-compose.yaml to set the UID/GID of the "node" user.
16 | ARG USER_UID=1000
17 | ARG USER_GID=$USER_UID
18 | RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then groupmod --gid $USER_GID node && usermod --uid $USER_UID --gid $USER_GID node; fi
19 |
20 | # [Optional] Uncomment this section to install additional OS packages.
21 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
22 | && apt-get -y install --no-install-recommends zsh
23 | RUN wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true
24 | RUN wget https://raw.githubusercontent.com/zakaziko99/agnosterzak-ohmyzsh-theme/master/agnosterzak.zsh-theme -P /home/node/.oh-my-zsh/themes
25 | RUN sed -i 's/ZSH\_THEME\=\".*\"/ZSH_THEME\=\"agnosterzak\"/' /home/node/.zshrc
26 | RUN echo "git config --global core.editor \"code --wait\"" >> /home/node/.zshrc
27 |
28 | # [Optional] Uncomment if you want to install an additional version of node using nvm
29 | # ARG EXTRA_NODE_VERSION=10
30 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
31 |
32 | # [Optional] Uncomment if you want to install more global node packages
33 | RUN su node -c "npm install -g @angular/cli @nestjs/cli"
34 |
--------------------------------------------------------------------------------
/server/src/swagger.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
3 | import axios from 'axios';
4 |
5 | interface ISwaggerMethod {
6 | get: (attr: string) => string;
7 | }
8 |
9 | export class Swagger {
10 | static title = 'API Docs example';
11 | static swaggerPath = 'api/docs';
12 | static config = new DocumentBuilder()
13 | .setTitle(Swagger.title)
14 | .setDescription('The API description')
15 | .setVersion('1.0')
16 | .setContact(
17 | 'thatkookooguy',
18 | 'github.com/thatkookooguy',
19 | 'thatkookooguy@kibibit.io'
20 | )
21 | .addTag(
22 | 'product',
23 | [
24 | 'Product is an example module to show how to ',
25 | 'add **swagger** documentation'
26 | ].join(''),
27 | {
28 | url: 'https://github.com/kibibit',
29 | description: 'See Docs'
30 | }
31 | )
32 | .build();
33 |
34 | static getOperationsSorter() {
35 | return (a: ISwaggerMethod, b: ISwaggerMethod) => {
36 | const methodsOrder = [
37 | 'get',
38 | 'post',
39 | 'put',
40 | 'patch',
41 | 'delete',
42 | 'options',
43 | 'trace'
44 | ];
45 | let result =
46 | methodsOrder.indexOf( a.get('method') ) -
47 | methodsOrder.indexOf( b.get('method') );
48 |
49 | if (result === 0) {
50 | result = a.get('path').localeCompare(b.get('path'));
51 | }
52 |
53 | return result;
54 | }
55 | }
56 |
57 | static async addSwagger(app: INestApplication) {
58 | const document = SwaggerModule.createDocument(app, Swagger.config);
59 | const swaggerCssResponse = await axios
60 | .get('https://kibibit.io/kibibit-assets/swagger/swagger.css');
61 | const customCss = swaggerCssResponse.data;
62 |
63 | SwaggerModule.setup(Swagger.swaggerPath, app, document, {
64 | customSiteTitle: Swagger.title,
65 | customCss,
66 | customJs: '//kibibit.io/kibibit-assets/swagger/swagger.js',
67 | swaggerOptions: {
68 | docExpansion: 'none',
69 | apisSorter: 'alpha',
70 | operationsSorter: Swagger.getOperationsSorter()
71 | }
72 | });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | lerna-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Snowpack dependency directory (https://snowpack.dev/)
47 | web_modules/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Microbundle cache
59 | .rpt2_cache/
60 | .rts2_cache_cjs/
61 | .rts2_cache_es/
62 | .rts2_cache_umd/
63 |
64 | # Optional REPL history
65 | .node_repl_history
66 |
67 | # Output of 'npm pack'
68 | *.tgz
69 |
70 | # Yarn Integrity file
71 | .yarn-integrity
72 |
73 | # dotenv environment variables file
74 | .env
75 | .env.test
76 |
77 | # parcel-bundler cache (https://parceljs.org/)
78 | .cache
79 | .parcel-cache
80 |
81 | # Next.js build output
82 | .next
83 | out
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 |
89 | # Gatsby files
90 | .cache/
91 | # Comment in the public line in if your project uses Gatsby and not Next.js
92 | # https://nextjs.org/blog/next-9-1#public-directory-support
93 | # public
94 |
95 | # vuepress build output
96 | .vuepress/dist
97 |
98 | # Serverless directories
99 | .serverless/
100 |
101 | # FuseBox cache
102 | .fusebox/
103 |
104 | # DynamoDB Local files
105 | .dynamodb/
106 |
107 | # TernJS port file
108 | .tern-port
109 |
110 | # Stores VSCode versions used for testing VSCode extensions
111 | .vscode-test
112 |
113 | # yarn v2
114 | .yarn/cache
115 | .yarn/unplugged
116 | .yarn/build-state.yml
117 | .yarn/install-state.gz
118 | .pnp.*
119 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve --port 10101 --poll=2000",
7 | "start:proxy": "ng serve --port 10101 --proxy-config ./proxy.conf.json --poll=2000",
8 | "build": "ng build",
9 | "test:watch": "ng test",
10 | "test": "ng test --no-watch --browsers ChromeHeadless",
11 | "test:cov": "ng test --no-watch --code-coverage --browsers ChromeHeadless",
12 | "lint": "ng lint",
13 | "lint:fix": "ng lint client --fix",
14 | "lint:html": "prettyhtml ./src/**/*.html --tab-width 2 --print-width 80 --wrapAttributes true --sortAttributes",
15 | "e2e": "ng e2e"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "~11.1.2",
20 | "@angular/cdk": "^11.1.2",
21 | "@angular/common": "~11.1.2",
22 | "@angular/compiler": "~11.1.2",
23 | "@angular/core": "~11.1.2",
24 | "@angular/forms": "~11.1.2",
25 | "@angular/material": "^11.1.2",
26 | "@angular/platform-browser": "~11.1.2",
27 | "@angular/platform-browser-dynamic": "~11.1.2",
28 | "@angular/router": "~11.1.2",
29 | "@kibibit/consologo": "^1.2.0",
30 | "rxjs": "~6.6.0",
31 | "tslib": "^2.0.0",
32 | "zone.js": "~0.10.2"
33 | },
34 | "devDependencies": {
35 | "@angular-devkit/build-angular": "~0.1101.4",
36 | "@angular-eslint/builder": "1.2.0",
37 | "@angular-eslint/eslint-plugin": "1.2.0",
38 | "@angular-eslint/eslint-plugin-template": "1.2.0",
39 | "@angular-eslint/schematics": "1.2.0",
40 | "@angular-eslint/template-parser": "1.2.0",
41 | "@angular/cli": "^11.1.4",
42 | "@angular/compiler-cli": "~11.1.2",
43 | "@starptech/prettyhtml": "^0.10.0",
44 | "@types/jasmine": "~3.5.0",
45 | "@types/jasminewd2": "~2.0.3",
46 | "@types/node": "^14.14.25",
47 | "@typescript-eslint/eslint-plugin": "4.14.2",
48 | "@typescript-eslint/parser": "4.14.2",
49 | "codelyzer": "^6.0.0",
50 | "eslint": "^7.19.0",
51 | "eslint-plugin-import": "2.22.1",
52 | "eslint-plugin-jsdoc": "31.6.1",
53 | "eslint-plugin-prefer-arrow": "1.2.2",
54 | "jasmine-core": "~3.6.0",
55 | "jasmine-spec-reporter": "~5.0.0",
56 | "karma": "^5.2.3",
57 | "karma-chrome-launcher": "~3.1.0",
58 | "karma-coverage": "^2.0.3",
59 | "karma-coverage-istanbul-reporter": "~3.0.2",
60 | "karma-jasmine": "~4.0.0",
61 | "karma-jasmine-html-reporter": "^1.5.0",
62 | "karma-spec-reporter": "0.0.32",
63 | "protractor": "~7.0.0",
64 | "ts-node": "~9.1.1",
65 | "tslint": "~6.1.0",
66 | "typescript": "~4.1.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @kibibit/kb-server-client-template
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | DESCRIPTION
18 |
19 |
20 |
21 | ## Installation
22 | ## Usage
23 |
24 | ## Contributors ✨
25 |
26 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
42 |
43 | ## Stay in touch
44 |
45 | - Author - [Neil Kalman](https://github.com/thatkookooguy)
46 | - Website - [https://github.com/kibibit](https://github.com/kibibit)
47 | - StackOverflow - [thatkookooguy](https://stackoverflow.com/users/1788884/thatkookooguy)
48 | - Twitter - [@thatkookooguy](https://twitter.com/thatkookooguy)
49 | - Twitter - [@kibibit_opensrc](https://twitter.com/kibibit_opensrc)
50 |
--------------------------------------------------------------------------------
/TEMPLATE_INSTRUCTIONS.md:
--------------------------------------------------------------------------------
1 | # Template Usage Overview
2 |
3 | ## Starting Up
4 |
5 | ### Prequisits
6 | - [vscode](https://code.visualstudio.com/)
7 | - [Docker](https://www.docker.com/) (**OPTIONAL**)
8 |
9 | ### Install the repo
10 | 1. On your local computer, copy the repo using `git clone`
11 | 2. Open the repository in vscode.
12 | 3. you can work in the following modes:
13 | - **[Devcontainer](https://code.visualstudio.com/docs/remote/containers)** - Create a docker container that runs the app with all the tools and extensions necassery. vscode will popup a notification to ask you if it should re-open the project inside a devcontainer. This will also start a mongodb database for the app to use against
14 | - **open locally** - run the repo locally. Should work but you might have some OS differences at edge-cases. You will need to provice a mongodb database link for the app to work against a database.
15 | 4. Open a terminal and run `npm install` inside the main folder. This will install all dependencies.
16 |
17 | ### Rename to your project name
18 | After the dependencies are installed, you need to change the project name from `kb-server-client-template` to whatever project you're developing. In order to do that, you can run:
19 | ```
20 | npm run init
21 | ```
22 | This will replace the important parts of the templates to your project name (which should be npm compliant).
23 |
24 | ## Run in Development mode
25 |
26 | ### Server
27 | In the main repo folder, run `npm run start:server` to start the server
28 | in watch mode. The server will be available at `localhost:10102`.
29 |
30 | API swagger documentation is available at `localhost:10102/api/docs` (or through the client proxy).
31 |
32 | A `Product` module is included as an example for a full module\service\controller feature with db.
33 |
34 | ### Client
35 | In the main repo folder, run `npm run start:client` to start the client in local proxy mode. The client will be available at `localhost:10101`. This will re-route all rest calls to
36 | `localhost:10101/api` to the server application running on `localhost:10102`.
37 |
38 | ## Run Production App
39 | In the main repo folder, run the following commands
40 | ```
41 | npm run build
42 | node .
43 | ```
44 |
45 | ## Run tests
46 |
47 | ### Server Unit-Tests
48 | In vscode, at the footer, click on `TESTS: Server Unit-tests and Coverage` or run `npm run test:server-unit`
49 | ### Client Unit-tests
50 | In vscode, at the footer, click on `TESTS: Client Unit-tests and Coverage` or run `npm run test:client-unit`
51 | ### API Tests
52 | In vscode, at the footer, click on `TESTS: API-tests` or run `npm run test:api`
53 | ### E2E Tests
54 | In vscode, at the footer, click on `TESTS: E2E-tests` or run `npm run test:e2e`
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "prebuild": "rimraf dist",
10 | "build": "nest build",
11 | "generate-barrels": "barrelsby --delete -d ./src -l below -q --exclude spec.ts",
12 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
13 | "start": "nest start",
14 | "start:dev": "nest start --watch",
15 | "start:debug": "nest start --debug --watch",
16 | "start:prod": "node dist/main",
17 | "lint": "eslint -c ./.eslintrc.js \"{src,apps,libs,test}/**/*.ts\"",
18 | "lint:fix": "eslint -c ./.eslintrc.js \"{src,apps,libs,test}/**/*.ts\" --fix",
19 | "test": "jest",
20 | "test:watch": "jest --watch",
21 | "test:cov": "jest --coverage",
22 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
23 | "test:e2e": "jest --config ./test/jest-e2e.json"
24 | },
25 | "dependencies": {},
26 | "devDependencies": {
27 | "@nestjs/cli": "^7.5.4",
28 | "@nestjs/schematics": "^7.2.7",
29 | "@nestjs/testing": "^7.6.11",
30 | "@types/express": "^4.17.11",
31 | "@types/fs-extra": "^9.0.7",
32 | "@types/jest": "^26.0.20",
33 | "@types/lodash": "^4.14.168",
34 | "@types/node": "^14.14.25",
35 | "@types/supertest": "^2.0.10",
36 | "@typescript-eslint/eslint-plugin": "^4.14.2",
37 | "@typescript-eslint/parser": "^4.14.2",
38 | "barrelsby": "^2.2.0",
39 | "eslint": "^7.19.0",
40 | "eslint-config-prettier": "^7.2.0",
41 | "eslint-plugin-prettier": "^3.3.1",
42 | "jest": "^26.6.3",
43 | "jest-mock-req-res": "^1.0.2",
44 | "prettier": "^2.2.1",
45 | "supertest": "^6.1.3",
46 | "ts-jest": "^26.5.0",
47 | "ts-loader": "^8.0.16",
48 | "ts-node": "^9.1.1",
49 | "tsconfig-paths": "^3.9.0",
50 | "typescript": "^4.1.3"
51 | },
52 | "jest": {
53 | "moduleFileExtensions": [
54 | "js",
55 | "json",
56 | "ts"
57 | ],
58 | "rootDir": "src",
59 | "testRegex": ".*\\.spec\\.ts$",
60 | "transform": {
61 | "^.+\\.(t|j)s$": "ts-jest"
62 | },
63 | "collectCoverageFrom": [
64 | "**/*.(t|j)s"
65 | ],
66 | "coverageDirectory": "../../coverage/server",
67 | "testEnvironment": "node",
68 | "moduleNameMapper": {
69 | "^@kb-server$": "",
70 | "^@kb-models$": "/models/index",
71 | "^@kb-abstracts$": "/abstracts/index",
72 | "^@kb-decorators$": "/decorators/index",
73 | "^@kb-filters$": "/filters/index",
74 | "^@kb-api$": "/api/index"
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/client/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | /**
3 | * This file includes polyfills needed by Angular and is loaded before the app.
4 | * You can add your own extra polyfills to this file.
5 | *
6 | * This file is divided into 2 sections:
7 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
8 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
9 | * file.
10 | *
11 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
12 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
13 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
14 | *
15 | * Learn more in https://angular.io/guide/browser-support
16 | */
17 |
18 | /***************************************************************************************************
19 | * BROWSER POLYFILLS
20 | */
21 |
22 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
23 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
24 |
25 | /**
26 | * Web Animations `@angular/platform-browser/animations`
27 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
28 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
29 | */
30 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
31 |
32 | /**
33 | * By default, zone.js will patch all possible macroTask and DomEvents
34 | * user can disable parts of macroTask/DomEvents patch by setting following flags
35 | * because those flags need to be set before `zone.js` being loaded, and webpack
36 | * will put import in the top of bundle, so user need to create a separate file
37 | * in this directory (for example: zone-flags.ts), and put the following flags
38 | * into that file, and then add the following code before importing zone.js.
39 | * import './zone-flags';
40 | *
41 | * The flags allowed in zone-flags.ts are listed here.
42 | *
43 | * The following flags will work for all browsers.
44 | *
45 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
46 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
47 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
48 | *
49 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
50 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
51 | *
52 | * (window as any).__Zone_enable_cross_context_check = true;
53 | *
54 | */
55 |
56 | /***************************************************************************************************
57 | * Zone JS is required by default for Angular itself.
58 | */
59 | import 'zone.js/dist/zone'; // Included with Angular CLI.
60 |
61 |
62 | /***************************************************************************************************
63 | * APPLICATION IMPORTS
64 | */
65 |
--------------------------------------------------------------------------------
/client/src/app/angular-material/angular-material.module.ts:
--------------------------------------------------------------------------------
1 | import { OverlayModule } from '@angular/cdk/overlay';
2 | import { PortalModule } from '@angular/cdk/portal';
3 | import { CdkTreeModule } from '@angular/cdk/tree';
4 | import { CommonModule } from '@angular/common';
5 | import { NgModule } from '@angular/core';
6 | import { MatAutocompleteModule } from '@angular/material/autocomplete';
7 | import { MatBadgeModule } from '@angular/material/badge';
8 | import { MatButtonModule } from '@angular/material/button';
9 | import { MatButtonToggleModule } from '@angular/material/button-toggle';
10 | import { MatCardModule } from '@angular/material/card';
11 | import { MatCheckboxModule } from '@angular/material/checkbox';
12 | import { MatChipsModule } from '@angular/material/chips';
13 | import { MatRippleModule } from '@angular/material/core';
14 | import { MatDatepickerModule } from '@angular/material/datepicker';
15 | import { MatDividerModule } from '@angular/material/divider';
16 | import { MatExpansionModule } from '@angular/material/expansion';
17 | import { MatFormFieldModule } from '@angular/material/form-field';
18 | import { MatGridListModule } from '@angular/material/grid-list';
19 | import { MatIconModule } from '@angular/material/icon';
20 | import { MatInputModule } from '@angular/material/input';
21 | import { MatListModule } from '@angular/material/list';
22 | import { MatMenuModule } from '@angular/material/menu';
23 | import { MatPaginatorModule } from '@angular/material/paginator';
24 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
25 | import { MatRadioModule } from '@angular/material/radio';
26 | import { MatSelectModule } from '@angular/material/select';
27 | import { MatSidenavModule } from '@angular/material/sidenav';
28 | import { MatSnackBarModule } from '@angular/material/snack-bar';
29 | import { MatSortModule } from '@angular/material/sort';
30 | import { MatTableModule } from '@angular/material/table';
31 | import { MatTabsModule } from '@angular/material/tabs';
32 | import { MatToolbarModule } from '@angular/material/toolbar';
33 | import { MatTooltipModule } from '@angular/material/tooltip';
34 | import { MatTreeModule } from '@angular/material/tree';
35 |
36 |
37 | const materialModules = [
38 | CdkTreeModule,
39 | MatAutocompleteModule,
40 | MatButtonModule,
41 | MatCardModule,
42 | MatCheckboxModule,
43 | MatChipsModule,
44 | MatDividerModule,
45 | MatExpansionModule,
46 | MatIconModule,
47 | MatInputModule,
48 | MatListModule,
49 | MatMenuModule,
50 | MatProgressSpinnerModule,
51 | MatPaginatorModule,
52 | MatRippleModule,
53 | MatSelectModule,
54 | MatSidenavModule,
55 | MatSnackBarModule,
56 | MatSortModule,
57 | MatTableModule,
58 | MatTabsModule,
59 | MatToolbarModule,
60 | MatFormFieldModule,
61 | MatButtonToggleModule,
62 | MatTreeModule,
63 | OverlayModule,
64 | PortalModule,
65 | MatBadgeModule,
66 | MatGridListModule,
67 | MatRadioModule,
68 | MatDatepickerModule,
69 | MatTooltipModule
70 | ];
71 |
72 | @NgModule({
73 | imports: [
74 | CommonModule,
75 | ...materialModules
76 | ],
77 | exports: [
78 | ...materialModules
79 | ]
80 | })
81 |
82 | export class AngularMaterialModule { }
83 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "dev:start",
6 | "detail": "Start both server & client for development",
7 | "dependsOn": [
8 | "dev:server",
9 | "dev:client-proxy"
10 | ],
11 | "problemMatcher": [],
12 | "options": {
13 | "statusbar": {
14 | "color": "#35AC5E"
15 | }
16 | }
17 | },
18 | {
19 | "label": "dev:client-proxy",
20 | "detail": "client in proxy\\watch mode",
21 | "type": "npm",
22 | "script": "start:proxy",
23 | "path": "client/",
24 | "problemMatcher": [],
25 | "presentation": {
26 | "reveal": "always",
27 | "panel": "shared",
28 | "group": "devwatch"
29 | },
30 | "options": {
31 | "statusbar": {
32 | "color": "#35AC5E"
33 | }
34 | }
35 | },
36 | {
37 | "label": "dev:server",
38 | "detail": "server in watch mode",
39 | "type": "npm",
40 | "script": "start:server",
41 | "problemMatcher": [],
42 | "presentation": {
43 | "reveal": "always",
44 | "panel": "shared",
45 | "group": "devwatch"
46 | },
47 | "options": {
48 | "statusbar": {
49 | "color": "#35AC5E"
50 | }
51 | }
52 | },
53 | {
54 | "type": "npm",
55 | "script": "build",
56 | "group": "build",
57 | "problemMatcher": [],
58 | "label": "Build Production",
59 | "detail": "Will build both Server & Client. Output in ./dist/",
60 | "options": {
61 | "statusbar": {
62 | "color": "#209CEE"
63 | }
64 | }
65 | },
66 | {
67 | "type": "npm",
68 | "script": "test:cov",
69 | "path": "server/",
70 | "problemMatcher": [],
71 | "label": "TESTS: Server Unit-tests and Coverage",
72 | "detail": "Run Server unit-tests",
73 | "options": {
74 | "statusbar": {
75 | "color": "#FFDD57"
76 | }
77 | }
78 | },
79 | {
80 | "type": "npm",
81 | "script": "test:e2e",
82 | "path": "server/",
83 | "problemMatcher": [],
84 | "label": "TESTS: API-tests",
85 | "detail": "Run API Tests (server e2e)",
86 | "options": {
87 | "statusbar": {
88 | "color": "#FFDD57"
89 | }
90 | }
91 | },
92 | {
93 | "type": "npm",
94 | "script": "e2e",
95 | "path": "client/",
96 | "problemMatcher": [],
97 | "label": "TESTS: E2E-tests",
98 | "detail": "Run E2E tests (client e2e. includes HTML interaction)",
99 | "options": {
100 | "statusbar": {
101 | "color": "#FFDD57"
102 | }
103 | }
104 | },
105 | {
106 | "type": "npm",
107 | "script": "test:cov",
108 | "path": "client/",
109 | "group": "test",
110 | "label": "TEST: Client Unit-tests and Coverage",
111 | "detail": "Run client unit-tests",
112 | "isBackground": true,
113 | "problemMatcher": "$karma-jasmine2",
114 | "options": {
115 | "statusbar": {
116 | "color": "#FFDD57"
117 | }
118 | }
119 | }
120 | ]
121 | }
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/typescript-node
3 | {
4 | "name": "Node.js & TypeScript",
5 | "dockerComposeFile": "docker-compose.yml",
6 | "service": "app",
7 | "workspaceFolder": "/workspace",
8 | // Set *default* container specific settings.json values on container create.
9 | "settings": {
10 | "terminal.integrated.shell.linux": "/bin/zsh",
11 | "peacock.color": "#3334f4",
12 | "editor.tabSize": 2,
13 | "editor.insertSpaces": true,
14 | "editor.detectIndentation": false,
15 | "editor.rulers": [80],
16 | "editor.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace",
17 | "editor.matchBrackets": "always",
18 | // Terminal
19 | "terminal.integrated.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace",
20 | "terminal.integrated.fontSize": 14,
21 | // Workbench
22 | "workbench.colorTheme": "Andromeda",
23 | "workbench.editor.showIcons": true,
24 | "workbench.iconTheme": "vs-seti",
25 | // Bracket Pair Colorizer
26 | "bracketPairColorizer.colorMode": "Consecutive",
27 | "bracketPairColorizer.forceUniqueOpeningColor": true,
28 | "bracketPairColorizer.showBracketsInGutter": true,
29 | "window.title": "${activeEditorShort}${separator}${rootName} [kibibit]",
30 | "typescriptHero.imports.stringQuoteStyle": "'",
31 | "typescriptHero.imports.grouping": [
32 | "Plains",
33 | "Modules",
34 | "/^@kb-/",
35 | "Workspace"
36 |
37 | ],
38 | "typescriptHero.imports.organizeOnSave": true,
39 | "typescriptHero.imports.multiLineTrailingComma": false,
40 | "typescriptHero.imports.multiLineWrapThreshold": 80,
41 | "typescriptHero.imports.insertSpaceBeforeAndAfterImportBraces": true,
42 | "typescriptHero.imports.insertSemicolons": true,
43 | "editor.codeActionsOnSave": {
44 | "source.fixAll.eslint": true,
45 | },
46 | "eslint.format.enable": true,
47 | "vsicons.presets.angular": true,
48 | "eslint.workingDirectories": [
49 | "client/",
50 | "server/"
51 | ]
52 | },
53 |
54 | // Add the IDs of extensions you want installed when the container is created.
55 | "extensions": [
56 | "dbaeumer.vscode-eslint",
57 | "rbbit.typescript-hero",
58 | "coenraads.bracket-pair-colorizer",
59 | "orta.vscode-jest",
60 | "wix.vscode-import-cost",
61 | "actboy168.tasks",
62 | "johnpapa.vscode-peacock",
63 | "angular.ng-template",
64 | "abhijoybasak.nestjs-files",
65 | "eamodio.gitlens",
66 | "codeandstuff.package-json-upgrade",
67 | "mongodb.mongodb-vscode",
68 | "ms-azuretools.vscode-docker",
69 | "jock.svg"
70 | ],
71 |
72 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
73 | "forwardPorts": [
74 | 10101, // dev client
75 | 10102, // dev server
76 | 27017 // mongodb instance
77 | ],
78 |
79 | // Use 'postCreateCommand' to run commands after the container is created.
80 | // "postCreateCommand": "yarn install",
81 | // "mounts": [
82 | // "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
83 | // ],
84 |
85 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
86 | "remoteUser": "node"
87 | }
--------------------------------------------------------------------------------
/server/src/api/product/product.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | Controller,
4 | Logger,
5 | NotFoundException,
6 | Param,
7 | UseFilters
8 | } from '@nestjs/common';
9 | import { ApiTags } from '@nestjs/swagger';
10 |
11 | import {
12 | GetAll,
13 | GetOne,
14 | KbDelete,
15 | KbPatch,
16 | KbPost,
17 | KbPut
18 | } from '@kb-decorators';
19 | import { KbValidationExceptionFilter } from '@kb-filters';
20 | import { Product } from '@kb-models';
21 |
22 | import { ProductService } from './product.service';
23 |
24 | @Controller('api/product')
25 | @ApiTags('product')
26 | @UseFilters(new KbValidationExceptionFilter())
27 | export class ProductController {
28 | private readonly logger = new Logger(ProductController.name);
29 |
30 | constructor(private readonly productService: ProductService) {}
31 |
32 | @GetAll(Product)
33 | async getAllProducts() {
34 | const productsDB = await this.productService.findAllAsync();
35 | const products = productsDB
36 | .map((productDb) => new Product(productDb.toObject()));
37 |
38 | return products;
39 | }
40 |
41 | @GetOne(Product, ':name')
42 | async getProduct(@Param('name') name: string) {
43 | const product = await this.productService.findByName(name);
44 |
45 | if (!product) {
46 | throw new NotFoundException(`Product with name ${ name } not found`);
47 | }
48 |
49 | // will show secret fields as well!
50 | this.logger.log('Full Product');
51 | // will log only public fields!
52 | this.logger.log(product);
53 | // DANGER! WILL LOG EVERYTHING!
54 | console.log(product);
55 |
56 | // will only include exposed fields
57 | return product;
58 | }
59 |
60 | @KbPost(Product)
61 | async createProduct(@Body() item: Product) {
62 |
63 | const product = await this.productService.create(item);
64 |
65 | return product
66 | }
67 |
68 | @KbPatch(Product, ':name')
69 | async changeProduct(
70 | @Param('name') name: string,
71 | @Body() changes: Product
72 | ) {
73 | const existingProductDB = await this.productService.findOneAsync({ name });
74 |
75 | if (!existingProductDB) {
76 | throw new NotFoundException(`Product with name ${ name } not found`);
77 | }
78 |
79 | const existingProduct = new Product(existingProductDB.toObject());
80 | const product = await this.productService.updateAsync({
81 | ...existingProduct,
82 | ...changes
83 | })
84 | return product.toObject();
85 | }
86 |
87 | @KbPut(Product, ':name')
88 | async changeProduct2(
89 | @Param('name') name: string,
90 | @Body() changes: Product
91 | ) {
92 | const existingProductDB = await this.productService.findOneAsync({ name });
93 | const existingProduct = new Product(existingProductDB.toObject());
94 | const product = await this.productService.updateAsync({
95 | ...existingProduct,
96 | ...changes
97 | })
98 | return product.toObject();
99 | }
100 |
101 | @KbDelete(Product, ':name')
102 | async deleteProduct(@Param('name') name: string) {
103 | const product = await this.productService.deleteAsync({ name });
104 |
105 | const parsed = new Product(product.toObject());
106 |
107 | this.logger.log(parsed);
108 |
109 | // will only include exposed fields
110 | return parsed;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
6 | [travis-url]: https://travis-ci.org/nestjs/nest
7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
8 | [linux-url]: https://travis-ci.org/nestjs/nest
9 |
10 | A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 | ## Description
28 |
29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
30 |
31 | ## Installation
32 |
33 | ```bash
34 | $ npm install
35 | ```
36 |
37 | ## Running the app
38 |
39 | ```bash
40 | # development
41 | $ npm run start
42 |
43 | # watch mode
44 | $ npm run start:dev
45 |
46 | # production mode
47 | $ npm run start:prod
48 | ```
49 |
50 | ## Test
51 |
52 | ```bash
53 | # unit tests
54 | $ npm run test
55 |
56 | # e2e tests
57 | $ npm run test:e2e
58 |
59 | # test coverage
60 | $ npm run test:cov
61 | ```
62 |
63 | ## Support
64 |
65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
66 |
67 | ## Stay in touch
68 |
69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
70 | - Website - [https://nestjs.com](https://nestjs.com/)
71 | - Twitter - [@nestframework](https://twitter.com/nestframework)
72 |
73 | ## License
74 |
75 | Nest is [MIT licensed](LICENSE).
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kibibit/kb-server-client-template",
3 | "version": "0.0.0-development",
4 | "description": "",
5 | "main": "dist/server/main.js",
6 | "scripts": {
7 | "contributors:all": "cross-env HUSKY_SKIP_HOOKS=1 node ./tools/get-all-contributors.js",
8 | "contributors:add": "cross-env HUSKY_SKIP_HOOKS=1 all-contributors add",
9 | "contributors:generate": "cross-env HUSKY_SKIP_HOOKS=1 all-contributors generate",
10 | "install": "npm run install:server && npm run install:client",
11 | "install:client": "cd client && npm install",
12 | "install:server": "cd server && npm install",
13 | "build": "npm run build:server && npm run build:client",
14 | "build:client": "cd ./client && npm run build",
15 | "build:server": "cd ./server && npm run build",
16 | "start:client": "cd ./client && npm run start",
17 | "start:server": "cd ./server && npm run start:dev",
18 | "init": "node ./tools/replace-template-string.js",
19 | "generate-barrels": "barrelsby --delete -d ./src -l below -q",
20 | "semantic-release:setup": "semantic-release-cli setup",
21 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
22 | "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
23 | "test": "echo \"Error: no test specified\" && exit 1",
24 | "test:server-unit": "cd server && npm run test",
25 | "test:client-unit": "cd client && npm run test",
26 | "test:api": "cd server && npm run test:e2e",
27 | "test:e2e": "cd client && npm run test:e2e",
28 | "semantic-release": "semantic-release"
29 | },
30 | "author": "thatkookooguy ",
31 | "license": "MIT",
32 | "devDependencies": {
33 | "@commitlint/cli": "^11.0.0",
34 | "@commitlint/config-angular": "^11.0.0",
35 | "@commitlint/config-conventional": "^11.0.0",
36 | "@types/mongoose": "^5.10.3",
37 | "@typescript-eslint/eslint-plugin": "^4.14.2",
38 | "@typescript-eslint/parser": "^4.14.2",
39 | "all-contributors-cli": "^6.20.0",
40 | "commitizen": "^4.2.3",
41 | "cross-env": "^7.0.3",
42 | "cz-conventional-changelog": "^3.3.0",
43 | "cz-conventional-changelog-emoji": "^0.1.0",
44 | "eslint": "^7.19.0",
45 | "github-username": "^6.0.0",
46 | "gitlog": "^4.0.4",
47 | "husky": "^4.3.8",
48 | "ncp": "^2.0.0",
49 | "replace-in-file": "^6.2.0",
50 | "semantic-release": "^17.3.8",
51 | "semantic-release-cli": "^5.4.3",
52 | "shelljs": "^0.8.4",
53 | "ts-node": "^9.1.1",
54 | "typescript": "^4.1.3"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "https://github.com/Kibibit/kb-server-client-template.git"
59 | },
60 | "bugs": {
61 | "url": "https://github.com/Kibibit/kb-server-client-template/issues"
62 | },
63 | "dependencies": {
64 | "@kibibit/consologo": "^1.2.0",
65 | "@kibibit/kb-error": "^1.0.3",
66 | "@nestjs/common": "^7.6.11",
67 | "@nestjs/core": "^7.6.11",
68 | "@nestjs/mongoose": "^7.2.3",
69 | "@nestjs/platform-express": "^7.6.11",
70 | "@nestjs/swagger": "^4.7.12",
71 | "@typegoose/typegoose": "^7.4.8",
72 | "axios": "^0.21.1",
73 | "class-transformer": "^0.3.2",
74 | "class-validator": "^0.13.1",
75 | "find-root": "^1.1.0",
76 | "fs-extra": "^9.1.0",
77 | "lodash": "^4.17.20",
78 | "mongoose": "^5.10.18",
79 | "reflect-metadata": "^0.1.13",
80 | "rimraf": "^3.0.2",
81 | "rxjs": "^6.6.3",
82 | "swagger-ui-express": "^4.1.6"
83 | },
84 | "config": {
85 | "commitizen": {
86 | "path": "./node_modules/cz-conventional-changelog-emoji"
87 | }
88 | },
89 | "husky": {
90 | "hooks": {
91 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",
92 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
93 | }
94 | },
95 | "publishConfig": {
96 | "access": "public"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/client/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rulesDirectory": [
4 | "codelyzer"
5 | ],
6 | "rules": {
7 | "align": {
8 | "options": [
9 | "parameters",
10 | "statements"
11 | ]
12 | },
13 | "array-type": false,
14 | "arrow-return-shorthand": true,
15 | "curly": true,
16 | "deprecation": {
17 | "severity": "warning"
18 | },
19 | "eofline": true,
20 | "import-blacklist": [
21 | true,
22 | "rxjs/Rx"
23 | ],
24 | "import-spacing": true,
25 | "indent": {
26 | "options": [
27 | "spaces"
28 | ]
29 | },
30 | "max-classes-per-file": false,
31 | "max-line-length": [
32 | true,
33 | 140
34 | ],
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-console": [
47 | true,
48 | "debug",
49 | "info",
50 | "time",
51 | "timeEnd",
52 | "trace"
53 | ],
54 | "no-empty": false,
55 | "no-inferrable-types": [
56 | true,
57 | "ignore-params"
58 | ],
59 | "no-non-null-assertion": true,
60 | "no-redundant-jsdoc": true,
61 | "no-switch-case-fall-through": true,
62 | "no-var-requires": false,
63 | "object-literal-key-quotes": [
64 | true,
65 | "as-needed"
66 | ],
67 | "quotemark": [
68 | true,
69 | "single"
70 | ],
71 | "semicolon": {
72 | "options": [
73 | "always"
74 | ]
75 | },
76 | "space-before-function-paren": {
77 | "options": {
78 | "anonymous": "never",
79 | "asyncArrow": "always",
80 | "constructor": "never",
81 | "method": "never",
82 | "named": "never"
83 | }
84 | },
85 | "typedef": [
86 | true,
87 | "call-signature"
88 | ],
89 | "typedef-whitespace": {
90 | "options": [
91 | {
92 | "call-signature": "nospace",
93 | "index-signature": "nospace",
94 | "parameter": "nospace",
95 | "property-declaration": "nospace",
96 | "variable-declaration": "nospace"
97 | },
98 | {
99 | "call-signature": "onespace",
100 | "index-signature": "onespace",
101 | "parameter": "onespace",
102 | "property-declaration": "onespace",
103 | "variable-declaration": "onespace"
104 | }
105 | ]
106 | },
107 | "variable-name": {
108 | "options": [
109 | "ban-keywords",
110 | "check-format",
111 | "allow-pascal-case"
112 | ]
113 | },
114 | "whitespace": {
115 | "options": [
116 | "check-branch",
117 | "check-decl",
118 | "check-operator",
119 | "check-separator",
120 | "check-type",
121 | "check-typecast"
122 | ]
123 | },
124 | "component-class-suffix": true,
125 | "contextual-lifecycle": true,
126 | "directive-class-suffix": true,
127 | "no-conflicting-lifecycle": true,
128 | "no-host-metadata-property": true,
129 | "no-input-rename": true,
130 | "no-inputs-metadata-property": true,
131 | "no-output-native": true,
132 | "no-output-on-prefix": true,
133 | "no-output-rename": true,
134 | "no-outputs-metadata-property": true,
135 | "template-banana-in-box": true,
136 | "template-no-negated-async": true,
137 | "use-lifecycle-interface": true,
138 | "use-pipe-transform-interface": true,
139 | "directive-selector": [
140 | true,
141 | "attribute",
142 | "app",
143 | "camelCase"
144 | ],
145 | "component-selector": [
146 | true,
147 | "element",
148 | "app",
149 | "kebab-case"
150 | ]
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/client/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "client": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "",
14 | "sourceRoot": "src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:browser",
19 | "options": {
20 | "outputPath": "../dist/client",
21 | "index": "src/index.html",
22 | "main": "src/main.ts",
23 | "polyfills": "src/polyfills.ts",
24 | "tsConfig": "tsconfig.app.json",
25 | "aot": true,
26 | "assets": [
27 | "src/favicon.ico",
28 | "src/assets"
29 | ],
30 | "styles": [
31 | "src/styles.scss"
32 | ],
33 | "scripts": []
34 | },
35 | "configurations": {
36 | "production": {
37 | "fileReplacements": [
38 | {
39 | "replace": "src/environments/environment.ts",
40 | "with": "src/environments/environment.prod.ts"
41 | }
42 | ],
43 | "optimization": true,
44 | "outputHashing": "all",
45 | "sourceMap": false,
46 | "extractCss": true,
47 | "namedChunks": false,
48 | "extractLicenses": true,
49 | "vendorChunk": false,
50 | "buildOptimizer": true,
51 | "budgets": [
52 | {
53 | "type": "initial",
54 | "maximumWarning": "2mb",
55 | "maximumError": "5mb"
56 | },
57 | {
58 | "type": "anyComponentStyle",
59 | "maximumWarning": "6kb",
60 | "maximumError": "10kb"
61 | }
62 | ]
63 | }
64 | }
65 | },
66 | "serve": {
67 | "builder": "@angular-devkit/build-angular:dev-server",
68 | "options": {
69 | "browserTarget": "client:build"
70 | },
71 | "configurations": {
72 | "production": {
73 | "browserTarget": "client:build:production"
74 | }
75 | }
76 | },
77 | "extract-i18n": {
78 | "builder": "@angular-devkit/build-angular:extract-i18n",
79 | "options": {
80 | "browserTarget": "client:build"
81 | }
82 | },
83 | "test": {
84 | "builder": "@angular-devkit/build-angular:karma",
85 | "options": {
86 | "main": "src/test.ts",
87 | "polyfills": "src/polyfills.ts",
88 | "tsConfig": "tsconfig.spec.json",
89 | "karmaConfig": "karma.conf.js",
90 | "assets": [
91 | "src/favicon.ico",
92 | "src/assets"
93 | ],
94 | "styles": [
95 | "src/styles.scss"
96 | ],
97 | "scripts": []
98 | }
99 | },
100 | "lint": {
101 | "builder": "@angular-eslint/builder:lint",
102 | "options": {
103 | "lintFilePatterns": [
104 | "src/**/*.ts",
105 | "src/**/*.html"
106 | ]
107 | }
108 | },
109 | "e2e": {
110 | "builder": "@angular-devkit/build-angular:protractor",
111 | "options": {
112 | "protractorConfig": "e2e/protractor.conf.js",
113 | "devServerTarget": "client:serve"
114 | },
115 | "configurations": {
116 | "production": {
117 | "devServerTarget": "client:serve:production"
118 | }
119 | }
120 | }
121 | }
122 | }
123 | },
124 | "defaultProject": "client"
125 | }
126 |
--------------------------------------------------------------------------------
/server/src/abstracts/base.service.abstract.ts:
--------------------------------------------------------------------------------
1 | import { InternalServerErrorException } from '@nestjs/common';
2 | import { DocumentType, ReturnModelType } from '@typegoose/typegoose';
3 | import { AnyParamConstructor } from '@typegoose/typegoose/lib/types';
4 | import { MongoError } from 'mongodb';
5 | import { Document, DocumentQuery, Query, Types } from 'mongoose';
6 |
7 | import { BaseModel } from './base.model.abstract';
8 |
9 |
10 | type QueryList = DocumentQuery<
11 | Array>,
12 | DocumentType
13 | >;
14 | type QueryItem = DocumentQuery<
15 | DocumentType,
16 | DocumentType
17 | >;
18 |
19 | export abstract class BaseService {
20 | protected model: ReturnModelType>;
21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
22 | protected class: any;
23 |
24 | protected constructor(
25 | model: ReturnModelType>,
26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
27 | serviceClass: any
28 | ) {
29 | this.model = model;
30 | this.class = serviceClass;
31 | }
32 |
33 | protected static throwMongoError(err: MongoError): void {
34 | throw new InternalServerErrorException(err, err.errmsg);
35 | }
36 |
37 | protected static toObjectId(id: string): Types.ObjectId {
38 | try {
39 | return Types.ObjectId(id);
40 | } catch (e) {
41 | this.throwMongoError(e);
42 | }
43 | }
44 |
45 | createModel(doc?: Partial): T {
46 | return new this.model(doc);
47 | }
48 |
49 | findAll(filter = {}): QueryList {
50 | return this.model.find(filter);
51 | }
52 |
53 | async findAllAsync(filter = {}): Promise>> {
54 | try {
55 | return await this.findAll(filter).exec();
56 | } catch (e) {
57 | BaseService.throwMongoError(e);
58 | }
59 | }
60 |
61 | findOne(filter = {}): QueryItem {
62 | return this.model.findOne(filter);
63 | }
64 |
65 | async findOneAsync(filter = {}): Promise> {
66 | try {
67 | return await this.findOne(filter).exec();
68 | } catch (e) {
69 | BaseService.throwMongoError(e);
70 | }
71 | }
72 |
73 | findById(id: string): QueryItem {
74 | return this.model.findById(BaseService.toObjectId(id));
75 | }
76 |
77 | async findByIdAsync(id: string): Promise> {
78 | try {
79 | return await this.findById(id).exec();
80 | } catch (e) {
81 | BaseService.throwMongoError(e);
82 | }
83 | }
84 |
85 | async create(item: T): Promise {
86 | try {
87 | const persistedItem = await this.model.create(item);
88 | return new this.class(persistedItem.toObject());
89 | } catch (e) {
90 | BaseService.throwMongoError(e);
91 | }
92 | }
93 |
94 | delete(filter = {}): QueryItem {
95 | return this.model.findOneAndDelete(filter);
96 | }
97 |
98 | async deleteAsync(filter = {}): Promise> {
99 | try {
100 | return await this.delete(filter).exec();
101 | } catch (e) {
102 | BaseService.throwMongoError(e);
103 | }
104 | }
105 |
106 | deleteById(id: string): QueryItem {
107 | return this.model.findByIdAndDelete(BaseService.toObjectId(id));
108 | }
109 |
110 | async deleteByIdAsync(id: string): Promise> {
111 | try {
112 | return await this.deleteById(id).exec();
113 | } catch (e) {
114 | BaseService.throwMongoError(e);
115 | }
116 | }
117 |
118 | update(item: Partial): QueryItem {
119 | return this.model.findByIdAndUpdate(BaseService.toObjectId(item.id), item, {
120 | new: true
121 | });
122 | }
123 |
124 | async updateAsync(item: Partial): Promise> {
125 | try {
126 | return await this.update(item).exec();
127 | } catch (e) {
128 | BaseService.throwMongoError(e);
129 | }
130 | }
131 |
132 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
133 | count(filter = {}): Query> {
134 | return this.model.count(filter);
135 | }
136 |
137 | async countAsync(filter = {}): Promise {
138 | try {
139 | return await this.count(filter);
140 | } catch (e) {
141 | BaseService.throwMongoError(e);
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------