├── 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 | achievibit Logo 3 | 4 |

5 | @kibibit/kb-server-client-template 6 |

7 |

8 |

9 | 10 |

11 |

12 | 13 | All Contributors 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 | 31 | 32 | 33 | 34 |

Neil Kalman

💻 📖 🎨 🚧 🚇 ⚠️
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 | Nest Logo 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 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 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 | --------------------------------------------------------------------------------