├── monorepo-example-www ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── app.component.scss │ │ ├── containers │ │ │ ├── home │ │ │ │ ├── home.component.css │ │ │ │ ├── index.ts │ │ │ │ ├── home.module.ts │ │ │ │ ├── home.component.ts │ │ │ │ ├── home.component.spec.ts │ │ │ │ └── home.component.html │ │ │ ├── sign-in │ │ │ │ ├── sign-in.component.css │ │ │ │ ├── index.ts │ │ │ │ ├── sign-in.module.ts │ │ │ │ ├── sign-in.component.spec.ts │ │ │ │ ├── sign-in.component.ts │ │ │ │ └── sign-in.component.html │ │ │ ├── page-not-found │ │ │ │ ├── page-not-found.component.css │ │ │ │ ├── page-not-found.component.html │ │ │ │ ├── index.ts │ │ │ │ ├── page-not-found.module.ts │ │ │ │ ├── page-not-found.component.ts │ │ │ │ └── page-not-found.component.spec.ts │ │ │ └── index.ts │ │ ├── components │ │ │ └── toolbar │ │ │ │ ├── toolbar.component.scss │ │ │ │ ├── toolbar.component.ts │ │ │ │ ├── toolbar.component.html │ │ │ │ ├── toolbar-component.module.ts │ │ │ │ └── toolbar.component.spec.ts │ │ ├── effects │ │ │ ├── index.ts │ │ │ ├── app.effects.spec.ts │ │ │ └── app.effects.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── window.service.ts │ │ │ ├── location.service.ts │ │ │ ├── user.service.spec.ts │ │ │ ├── window.service.spec.ts │ │ │ ├── location.service.spec.ts │ │ │ ├── app-config.service.spec.ts │ │ │ ├── route-guard.service.spec.ts │ │ │ ├── user.service.ts │ │ │ ├── route-guard.service.ts │ │ │ └── app-config.service.ts │ │ ├── reducers │ │ │ ├── app.reducer.spec.ts │ │ │ ├── index.ts │ │ │ └── app.reducer.ts │ │ ├── app.component.html │ │ ├── app-store.module.ts │ │ ├── actions │ │ │ └── app.actions.ts │ │ ├── angular-material.module.ts │ │ ├── app.component.spec.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.ts │ │ └── app.module.ts │ ├── favicon.ico │ ├── styles.scss │ ├── tsconfig.app.json │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── browserslist │ ├── main.ts │ ├── index.html │ ├── test.ts │ ├── karma.conf.js │ └── polyfills.ts ├── e2e │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ ├── tsconfig.e2e.json │ └── protractor.conf.js ├── .editorconfig ├── tsconfig.json ├── .gitignore ├── README.md ├── package.json ├── tslint.json └── angular.json ├── shared ├── src │ ├── public_api.ts │ ├── models │ │ ├── models.ts │ │ ├── index.ts │ │ ├── app-config.model.ts │ │ ├── session.model.ts │ │ └── user.model.ts │ └── adapters │ │ ├── adapter.ts │ │ ├── index.ts │ │ ├── app-config.adapter.ts │ │ ├── session.adapter.ts │ │ └── user.adapter.ts ├── package.json ├── tsconfig.json ├── .gitignore └── yarn.lock ├── monorepo-example-api ├── .prettierrc ├── src │ ├── config │ │ ├── index.ts │ │ ├── local-config.service.ts │ │ └── config.module.ts │ ├── controllers │ │ ├── index.ts │ │ ├── app.controller.ts │ │ ├── api.controller.spec.ts │ │ ├── app.controller.spec.ts │ │ └── api.controller.ts │ ├── storage │ │ ├── index.ts │ │ ├── memory-storage.service.spec.ts │ │ ├── memory-storage.service.ts │ │ └── storage.module.ts │ ├── middlewares │ │ ├── index.ts │ │ ├── cors.middleware.spec.ts │ │ ├── jwt-session.middleware.spec.ts │ │ ├── cors.middleware.ts │ │ └── jwt-session.middleware.ts │ ├── services │ │ ├── index.ts │ │ ├── app.service.ts │ │ ├── app.service.spec.ts │ │ ├── user.service.spec.ts │ │ ├── oauth.service.spec.ts │ │ ├── user.service.ts │ │ └── oauth.service.ts │ ├── main.ts │ ├── main.hmr.ts │ └── app.module.ts ├── nest-cli.json ├── nodemon.json ├── tsconfig.spec.json ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── tsconfig.json ├── .env-template ├── .gitignore ├── webpack.config.js ├── tslint.json ├── package.json └── README.md ├── README-screenshot.png ├── .gitignore └── README.md /monorepo-example-www/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/home/home.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/components/toolbar/toolbar.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/sign-in/sign-in.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/page-not-found/page-not-found.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/effects/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.effects'; 2 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-config.service'; 2 | -------------------------------------------------------------------------------- /shared/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './adapters'; 2 | export * from './models'; 3 | -------------------------------------------------------------------------------- /monorepo-example-api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/METACEO/monorepo-example/HEAD/README-screenshot.png -------------------------------------------------------------------------------- /monorepo-example-api/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config.module'; 2 | export * from './local-config.service'; 3 | -------------------------------------------------------------------------------- /monorepo-example-api/src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api.controller'; 2 | export * from './app.controller'; 3 | -------------------------------------------------------------------------------- /shared/src/models/models.ts: -------------------------------------------------------------------------------- 1 | export class BaseModel { 2 | constructor(public readonly id: string) { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /monorepo-example-api/src/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './memory-storage.service'; 2 | export * from './storage.module'; 3 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home.component'; 2 | export * from './home.module'; 3 | -------------------------------------------------------------------------------- /monorepo-example-api/src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cors.middleware'; 2 | export * from './jwt-session.middleware'; 3 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/page-not-found/page-not-found.component.html: -------------------------------------------------------------------------------- 1 |

2 | page-not-found works! 3 |

4 | -------------------------------------------------------------------------------- /monorepo-example-www/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/METACEO/monorepo-example/HEAD/monorepo-example-www/src/favicon.ico -------------------------------------------------------------------------------- /shared/src/adapters/adapter.ts: -------------------------------------------------------------------------------- 1 | export interface Adapter { 2 | adapt(item: any): T; 3 | partition(model: T): any; 4 | } 5 | -------------------------------------------------------------------------------- /monorepo-example-api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/sign-in/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sign-in.component'; 2 | export * from './sign-in.module'; 3 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home'; 2 | export * from './page-not-found'; 3 | export * from './sign-in'; 4 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.service'; 2 | export * from './oauth.service'; 3 | export * from './user.service'; 4 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/page-not-found/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page-not-found.component'; 2 | export * from './page-not-found.module'; 3 | -------------------------------------------------------------------------------- /shared/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-config.model'; 2 | export * from './models'; 3 | export * from './session.model'; 4 | export * from './user.model'; 5 | -------------------------------------------------------------------------------- /shared/src/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adapter'; 2 | export * from './app-config.adapter'; 3 | export * from './session.adapter'; 4 | export * from './user.adapter'; 5 | -------------------------------------------------------------------------------- /monorepo-example-api/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /monorepo-example-api/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"] 5 | }, 6 | "include": ["**/*.spec.ts", "**/*.d.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/app.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | root(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /monorepo-example-www/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/window.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class WindowService { 7 | 8 | constructor() { 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /shared/src/models/app-config.model.ts: -------------------------------------------------------------------------------- 1 | import {SessionModel} from './session.model'; 2 | 3 | export class AppConfigModel { 4 | constructor(public readonly features: string[], 5 | public readonly session: SessionModel) { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /monorepo-example-api/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 | } 10 | -------------------------------------------------------------------------------- /monorepo-example-www/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /monorepo-example-api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /monorepo-example-api/src/middlewares/cors.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import {CorsMiddleware} from './cors.middleware'; 2 | 3 | describe('CorsMiddleware', () => { 4 | it('should be defined', () => { 5 | expect(new CorsMiddleware()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /monorepo-example-api/src/middlewares/jwt-session.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { JwtSessionMiddleware } from './jwt-session.middleware'; 2 | 3 | describe('JwtSessionMiddleware', () => { 4 | it('should be defined', () => { 5 | expect(new JwtSessionMiddleware()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /monorepo-example-www/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /monorepo-example-www/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /monorepo-example-www/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://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 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /monorepo-example-api/src/middlewares/cors.middleware.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, MiddlewareFunction, NestMiddleware} from '@nestjs/common'; 2 | import * as cors from 'cors'; 3 | 4 | @Injectable() 5 | export class CorsMiddleware implements NestMiddleware { 6 | resolve(corsOptions): MiddlewareFunction { 7 | return cors(corsOptions); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /shared/src/models/session.model.ts: -------------------------------------------------------------------------------- 1 | import {BaseModel} from './models'; 2 | import {UserModel} from './user.model'; 3 | 4 | export class SessionModel extends BaseModel { 5 | constructor(public readonly id: string, 6 | public readonly user: UserModel, 7 | public readonly userId: string) { 8 | super(id); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /monorepo-example-www/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | url: { 4 | appConfig: 'https://monorepo-example/v1/app-config', 5 | home: 'https://monorepo-example/', 6 | signinGithub: 'https://monorepo-example/v1/sign-in/github', 7 | signout: 'https://monorepo-example/v1/api/sign-out', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /monorepo-example-www/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/location.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | import {environment} from '../../environments/environment'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class LocationService { 9 | 10 | constructor() { 11 | } 12 | 13 | goHome(): void { 14 | location.assign(environment.url.home); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /monorepo-example-api/src/controllers/app.controller.ts: -------------------------------------------------------------------------------- 1 | import {Get, Controller} from '@nestjs/common'; 2 | 3 | import * as servicesRoot from '../services'; 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private readonly appService: servicesRoot.AppService) { 8 | } 9 | 10 | @Get() 11 | root(): string { 12 | return this.appService.root(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # IDEs and editors 4 | /.idea 5 | .project 6 | .classpath 7 | .c9/ 8 | *.launch 9 | .settings/ 10 | *.sublime-workspace 11 | 12 | # IDE - VSCode 13 | .vscode/* 14 | !.vscode/settings.json 15 | !.vscode/tasks.json 16 | !.vscode/launch.json 17 | !.vscode/extensions.json 18 | 19 | # System Files 20 | .DS_Store 21 | Thumbs.db 22 | -------------------------------------------------------------------------------- /monorepo-example-www/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to monorepo-example-www!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {UserService} from './user.service'; 4 | 5 | describe('UserService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: UserService = TestBed.get(UserService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /monorepo-example-www/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /monorepo-example-api/src/main.hmr.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | declare const module: any; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | await app.listen(3000); 9 | 10 | if (module.hot) { 11 | module.hot.accept(); 12 | module.hot.dispose(() => app.close()); 13 | } 14 | } 15 | bootstrap(); 16 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/components/toolbar/toolbar.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {SessionModel} from '@monorepo-example/shared'; 3 | 4 | @Component({ 5 | selector: 'app-toolbar', 6 | templateUrl: './toolbar.component.html', 7 | styleUrls: ['./toolbar.component.scss'] 8 | }) 9 | export class ToolbarComponent { 10 | 11 | @Input() session: SessionModel = null; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/reducers/app.reducer.spec.ts: -------------------------------------------------------------------------------- 1 | import {reducer, initialState} from './app.reducer'; 2 | 3 | describe('App Reducer', () => { 4 | describe('unknown action', () => { 5 | it('should return the initial state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/window.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {WindowService} from './window.service'; 4 | 5 | describe('WindowService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: WindowService = TestBed.get(WindowService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | 4 | import {AppStoreModule} from '../../app-store.module'; 5 | import {HomeComponent} from './home.component'; 6 | 7 | @NgModule({ 8 | declarations: [HomeComponent], 9 | imports: [ 10 | CommonModule, 11 | AppStoreModule, 12 | ], 13 | }) 14 | export class HomeModule { 15 | } 16 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/location.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {LocationService} from './location.service'; 4 | 5 | describe('LocationService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: LocationService = TestBed.get(LocationService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/app-config.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {AppConfigService} from './app-config.service'; 4 | 5 | describe('AppConfigService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AppConfigService = TestBed.get(AppConfigService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Store} from '@ngrx/store'; 3 | 4 | @Component({ 5 | selector: 'app-home', 6 | templateUrl: './home.component.html', 7 | styleUrls: ['./home.component.css'] 8 | }) 9 | export class HomeComponent implements OnInit { 10 | 11 | constructor(private readonly store: Store) { 12 | } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/route-guard.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {RouteGuardService} from './route-guard.service'; 4 | 5 | describe('RouteGuardService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: RouteGuardService = TestBed.get(RouteGuardService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /monorepo-example-www/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /shared/src/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import {BaseModel} from './models'; 2 | 3 | export class UserModel extends BaseModel { 4 | constructor(public readonly id: string, 5 | public readonly auth: { 6 | oauth?: { 7 | github?: { 8 | id: string; 9 | }; 10 | }; 11 | }) { 12 | super(id); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /monorepo-example-www/src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/components/toolbar/toolbar.component.html: -------------------------------------------------------------------------------- 1 | 2 |
5 |
monorepo-example
6 | 7 |
8 | 9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/page-not-found/page-not-found.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | 4 | import {AppStoreModule} from '../../app-store.module'; 5 | import {PageNotFoundComponent} from './page-not-found.component'; 6 | 7 | @NgModule({ 8 | declarations: [PageNotFoundComponent], 9 | imports: [ 10 | CommonModule, 11 | AppStoreModule, 12 | ], 13 | }) 14 | export class PageNotFoundModule { 15 | } 16 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/page-not-found/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Store} from '@ngrx/store'; 3 | 4 | @Component({ 5 | selector: 'app-page-not-found', 6 | templateUrl: './page-not-found.component.html', 7 | styleUrls: ['./page-not-found.component.css'] 8 | }) 9 | export class PageNotFoundComponent implements OnInit { 10 | 11 | constructor(private readonly store: Store) { 12 | } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppService } from './app.service'; 3 | 4 | describe('AppService', () => { 5 | let service: AppService; 6 | 7 | beforeAll(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AppService], 10 | }).compile(); 11 | service = module.get(AppService); 12 | }); 13 | it('should be defined', () => { 14 | expect(service).toBeDefined(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/sign-in/sign-in.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | 4 | import {AngularMaterialModule} from '../../angular-material.module'; 5 | import {AppStoreModule} from '../../app-store.module'; 6 | import {SignInComponent} from './sign-in.component'; 7 | 8 | @NgModule({ 9 | declarations: [SignInComponent], 10 | imports: [ 11 | CommonModule, 12 | AngularMaterialModule, 13 | AppStoreModule, 14 | ] 15 | }) 16 | export class SignInModule { 17 | } 18 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserService } from './user.service'; 3 | 4 | describe('UserService', () => { 5 | let service: UserService; 6 | 7 | beforeAll(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserService], 10 | }).compile(); 11 | service = module.get(UserService); 12 | }); 13 | it('should be defined', () => { 14 | expect(service).toBeDefined(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /monorepo-example-api/src/controllers/api.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ApiController } from './api.controller'; 3 | 4 | describe('Api Controller', () => { 5 | let module: TestingModule; 6 | 7 | beforeAll(async () => { 8 | module = await Test.createTestingModule({ 9 | controllers: [ApiController], 10 | }).compile(); 11 | }); 12 | it('should be defined', () => { 13 | const controller: ApiController = module.get(ApiController); 14 | expect(controller).toBeDefined(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /monorepo-example-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./src" 15 | }, 16 | "include": [ 17 | "src/**/*" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | 5 | 6 | Sign Up 7 | Sign In 8 | 9 | 10 | Settings 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/oauth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {Test, TestingModule} from '@nestjs/testing'; 2 | import {OauthService} from './oauth.service'; 3 | 4 | describe('OauthService', () => { 5 | let service: OauthService; 6 | 7 | beforeAll(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OauthService], 10 | }).compile(); 11 | service = module.get(OauthService); 12 | }); 13 | it('should be defined', () => { 14 | expect(service).toBeDefined(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /monorepo-example-www/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MonorepoExampleWww 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading screen here. 15 | 16 | 17 | -------------------------------------------------------------------------------- /shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@monorepo-example/shared", 3 | "version": "1.0.0", 4 | "description": "Shared resources for the monorepo-example project.", 5 | "main": "./dist/public_api.js", 6 | "types": "./dist/public_api.d.ts", 7 | "scripts": { 8 | "tsc": "tsc", 9 | "build": "rimraf dist && tsc" 10 | }, 11 | "author": "James Allen", 12 | "license": "UNLICENSED", 13 | "dependencies": { 14 | "lodash": "^4.17.11", 15 | "typescript": "^3.1.6" 16 | }, 17 | "devDependencies": { 18 | "@types/lodash": "^4.14.116", 19 | "rimraf": "^2.6.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "outDir": "./dist", 11 | "rootDir": "./src", 12 | "sourceMap": true, 13 | "target": "es5", 14 | "lib": [ 15 | "es2015", 16 | "es2016", 17 | "es2017", 18 | "dom" 19 | ] 20 | }, 21 | "files": [ 22 | "./src/public_api.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /monorepo-example-api/src/storage/memory-storage.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {Test, TestingModule} from '@nestjs/testing'; 2 | 3 | import {MemoryStorageService} from './memory-storage.service'; 4 | 5 | describe('MemoryStorage', () => { 6 | let service: MemoryStorageService; 7 | 8 | beforeAll(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [MemoryStorageService], 11 | }).compile(); 12 | service = module.get(MemoryStorageService); 13 | }); 14 | it('should be defined', () => { 15 | expect(service).toBeDefined(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /monorepo-example-www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2018", 18 | "dom" 19 | ], 20 | "paths": { 21 | "@monorepo-example/shared": [ 22 | "../shared/src/public_api.ts" 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /monorepo-example-api/.env-template: -------------------------------------------------------------------------------- 1 | MONOREPO_EXAMPLE_JWT_SIGNINGKEY=have a good secret here 2 | MONOREPO_EXAMPLE_OAUTH_GITHUB_ACCESSTOKENURL=https://github.com/login/oauth/access_token 3 | MONOREPO_EXAMPLE_OAUTH_GITHUB_AUTHORIZEURL=https://github.com/login/oauth/authorize 4 | MONOREPO_EXAMPLE_OAUTH_GITHUB_CLIENTID=Your Github app's client id here 5 | MONOREPO_EXAMPLE_OAUTH_GITHUB_CLIENTSECRET=Your Github app's client secret here 6 | MONOREPO_EXAMPLE_OAUTH_GITHUB_REDIRECTURI=http://localhost:3000/api/oauth/github 7 | MONOREPO_EXAMPLE_OAUTH_GITHUB_USERAGENTHEADER=Your Name Here 8 | MONOREPO_EXAMPLE_OAUTH_GITHUB_USERPROFILEURL=https://api.github.com/user 9 | MONOREPO_EXAMPLE_STORAGE_TYPE=memory 10 | -------------------------------------------------------------------------------- /shared/.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 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /monorepo-example-www/.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 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app-store.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {EffectsModule} from '@ngrx/effects'; 3 | import {StoreModule} from '@ngrx/store'; 4 | import {StoreDevtoolsModule} from '@ngrx/store-devtools'; 5 | 6 | import {environment} from '../environments/environment'; 7 | import * as effectsRoot from './effects'; 8 | import {metaReducers, reducers} from './reducers'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | StoreModule.forRoot(reducers, {metaReducers}), 13 | !environment.production ? StoreDevtoolsModule.instrument() : [], 14 | EffectsModule.forRoot([ 15 | effectsRoot.AppEffects, 16 | ]), 17 | ], 18 | }) 19 | export class AppStoreModule { 20 | } 21 | -------------------------------------------------------------------------------- /monorepo-example-api/.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 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | .env 41 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/effects/app.effects.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed, inject} from '@angular/core/testing'; 2 | import {provideMockActions} from '@ngrx/effects/testing'; 3 | import {Observable} from 'rxjs'; 4 | 5 | import {AppEffects} from './app.effects'; 6 | 7 | describe('AppEffects', () => { 8 | let actions$: Observable; 9 | let effects: AppEffects; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | AppEffects, 15 | provideMockActions(() => actions$) 16 | ] 17 | }); 18 | 19 | effects = TestBed.get(AppEffects); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(effects).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/components/toolbar/toolbar-component.module.ts: -------------------------------------------------------------------------------- 1 | import {CommonModule} from '@angular/common'; 2 | import {NgModule} from '@angular/core'; 3 | import {FlexLayoutModule} from '@angular/flex-layout'; 4 | import {MatButtonModule} from '@angular/material/button'; 5 | import {MatToolbarModule} from '@angular/material/toolbar'; 6 | 7 | import {ToolbarComponent} from './toolbar.component'; 8 | 9 | @NgModule({ 10 | declarations: [ 11 | ToolbarComponent, 12 | ], 13 | imports: [ 14 | CommonModule, 15 | MatButtonModule, 16 | MatToolbarModule, 17 | FlexLayoutModule, 18 | ], 19 | exports: [ 20 | ToolbarComponent, 21 | ], 22 | }) 23 | export class ToolbarComponentModule { 24 | } 25 | -------------------------------------------------------------------------------- /monorepo-example-api/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /monorepo-example-www/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/actions/app.actions.ts: -------------------------------------------------------------------------------- 1 | import {AppConfigModel} from '@monorepo-example/shared'; 2 | import {Action} from '@ngrx/store'; 3 | 4 | export enum AppActionTypes { 5 | LoadInitialAppConfig = '[App] Load Initial App Config', 6 | UserClickedToolbarToSignout = '[App] User Clicked Toolbar To Signout', 7 | } 8 | 9 | export class LoadInitialAppConfig implements Action { 10 | readonly type = AppActionTypes.LoadInitialAppConfig; 11 | 12 | constructor(public readonly payload: AppConfigModel) { 13 | } 14 | } 15 | 16 | export class UserClickedToolbarToSignout implements Action { 17 | readonly type = AppActionTypes.UserClickedToolbarToSignout; 18 | } 19 | 20 | export type AppActions = 21 | | LoadInitialAppConfig 22 | | UserClickedToolbarToSignout; 23 | -------------------------------------------------------------------------------- /monorepo-example-api/src/controllers/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import { AppController } from './app.controller'; 4 | import { AppService } from '../app.service'; 5 | 6 | describe('AppController', () => { 7 | let app: TestingModule; 8 | 9 | beforeAll(async () => { 10 | app = await Test.createTestingModule({ 11 | controllers: [AppController], 12 | providers: [AppService], 13 | }).compile(); 14 | }); 15 | 16 | describe('root', () => { 17 | it('should return "Hello World!"', () => { 18 | const appController = app.get(AppController); 19 | expect(appController.root()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/components/toolbar/toolbar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ToolbarComponent } from './toolbar.component'; 4 | 5 | describe('ToolbarComponent', () => { 6 | let component: ToolbarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ToolbarComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ToolbarComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/effects/app.effects.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Actions, Effect, ofType} from '@ngrx/effects'; 3 | import {Action} from '@ngrx/store'; 4 | import {Observable} from 'rxjs'; 5 | import {tap} from 'rxjs/operators'; 6 | 7 | import {AppActionTypes} from '../actions/app.actions'; 8 | import {UserService} from "../services/user.service"; 9 | 10 | @Injectable() 11 | export class AppEffects { 12 | 13 | @Effect({dispatch: false}) 14 | userClickedToolbarDoSignout$: Observable = this.actions$.pipe( 15 | ofType(AppActionTypes.UserClickedToolbarToSignout), 16 | tap(() => this.userService.signOut()), 17 | ); 18 | 19 | constructor(private readonly actions$: Actions, 20 | private readonly userService: UserService) { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /monorepo-example-api/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const nodeExternals = require('webpack-node-externals'); 4 | 5 | module.exports = { 6 | entry: ['webpack/hot/poll?1000', './src/main.hmr.ts'], 7 | watch: true, 8 | target: 'node', 9 | externals: [ 10 | nodeExternals({ 11 | whitelist: ['webpack/hot/poll?1000'], 12 | }), 13 | ], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.tsx?$/, 18 | use: 'ts-loader', 19 | exclude: /node_modules/, 20 | }, 21 | ], 22 | }, 23 | mode: "development", 24 | resolve: { 25 | extensions: ['.tsx', '.ts', '.js'], 26 | }, 27 | plugins: [ 28 | new webpack.HotModuleReplacementPlugin(), 29 | ], 30 | output: { 31 | path: path.join(__dirname, 'dist'), 32 | filename: 'server.js', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/angular-material.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {FlexLayoutModule} from '@angular/flex-layout'; 3 | import {FormsModule, ReactiveFormsModule} from '@angular/forms'; 4 | import {MatButtonModule} from '@angular/material/button'; 5 | import {MatCardModule} from '@angular/material/card'; 6 | import {MatFormFieldModule} from '@angular/material/form-field'; 7 | import {MatInputModule} from '@angular/material/input'; 8 | import {MatToolbarModule} from '@angular/material/toolbar'; 9 | 10 | const modules = [ 11 | FlexLayoutModule, 12 | FormsModule, 13 | MatButtonModule, 14 | MatCardModule, 15 | MatFormFieldModule, 16 | MatInputModule, 17 | MatToolbarModule, 18 | ReactiveFormsModule, 19 | ]; 20 | 21 | @NgModule({ 22 | imports: modules, 23 | exports: modules, 24 | }) 25 | export class AngularMaterialModule { 26 | } 27 | -------------------------------------------------------------------------------- /monorepo-example-www/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {AppComponent} from './app.component'; 4 | import {Store, StoreModule} from '@ngrx/store'; 5 | 6 | describe('AppComponent', () => { 7 | let component: AppComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async () => { 12 | TestBed.configureTestingModule({ 13 | imports: [StoreModule.forRoot({})], 14 | declarations: [AppComponent] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(AppComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {HomeComponent} from './home.component'; 4 | import {Store, StoreModule} from '@ngrx/store'; 5 | 6 | describe('HomeComponent', () => { 7 | let component: HomeComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async () => { 12 | TestBed.configureTestingModule({ 13 | imports: [StoreModule.forRoot({})], 14 | declarations: [HomeComponent] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(HomeComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /monorepo-example-www/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 | url: { 8 | appConfig: 'http://localhost:3000/api/app-config', 9 | home: 'http://localhost:4200/', 10 | signinGithub: 'http://localhost:3000/api/sign-in/github', 11 | signout: 'http://localhost:3000/api/sign-out', 12 | }, 13 | }; 14 | 15 | /* 16 | * For easier debugging in development mode, you can import the following file 17 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 18 | * 19 | * This import should be commented out in production mode because it will have a negative impact 20 | * on performance if an error is thrown. 21 | */ 22 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 23 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/sign-in/sign-in.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {Store, StoreModule} from '@ngrx/store'; 3 | 4 | import {SignInComponent} from './sign-in.component'; 5 | 6 | describe('SignInComponent', () => { 7 | let component: SignInComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async () => { 12 | TestBed.configureTestingModule({ 13 | imports: [StoreModule.forRoot({})], 14 | declarations: [SignInComponent] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(SignInComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionReducer, 3 | ActionReducerMap, 4 | createFeatureSelector, 5 | createSelector, 6 | MetaReducer 7 | } from '@ngrx/store'; 8 | 9 | import {environment} from '../../environments/environment'; 10 | import * as fromApp from './app.reducer'; 11 | 12 | export interface State { 13 | app: fromApp.State; 14 | } 15 | 16 | export const reducers: ActionReducerMap = { 17 | app: fromApp.reducer, 18 | }; 19 | 20 | export const metaReducers: MetaReducer[] = !environment.production ? [] : []; 21 | 22 | export const selectApp = createFeatureSelector('app'); 23 | export const selectAppError = createSelector(selectApp, (state: fromApp.State) => state.error); 24 | export const selectAppFeatures = createSelector(selectApp, (state: fromApp.State) => state.features); 25 | export const selectAppInstantiated = createSelector(selectApp, (state: fromApp.State) => state.instantiated); 26 | export const selectAppSession = createSelector(selectApp, (state: fromApp.State) => state.session); 27 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import {HttpClient} from '@angular/common/http'; 2 | import {Injectable} from '@angular/core'; 3 | import {SessionModel} from '@monorepo-example/shared'; 4 | import {Store} from '@ngrx/store'; 5 | 6 | import {environment} from '../../environments/environment'; 7 | import * as reducersRoot from '../reducers'; 8 | import {LocationService} from './location.service'; 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class UserService { 14 | 15 | session: SessionModel = null; 16 | 17 | constructor(private readonly httpClient: HttpClient, 18 | private readonly locationService: LocationService, 19 | private readonly store: Store) { 20 | this.store.select(reducersRoot.selectAppSession).subscribe(session => this.session = session); 21 | } 22 | 23 | public signOut(): void { 24 | this.httpClient.post(environment.url.signout, {id: this.session.id}, {withCredentials: true}) 25 | .subscribe(() => this.locationService.goHome()) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/page-not-found/page-not-found.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {PageNotFoundComponent} from './page-not-found.component'; 4 | import {Store, StoreModule} from '@ngrx/store'; 5 | 6 | describe('PageNotFoundComponent', () => { 7 | let component: PageNotFoundComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async () => { 12 | TestBed.configureTestingModule({ 13 | imports: [StoreModule.forRoot({})], 14 | declarations: [PageNotFoundComponent] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(PageNotFoundComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /shared/src/adapters/app-config.adapter.ts: -------------------------------------------------------------------------------- 1 | import {get} from 'lodash'; 2 | 3 | import {AppConfigModel} from '../models'; 4 | import {Adapter} from './adapter'; 5 | 6 | export class AppConfigAdapter implements Adapter { 7 | 8 | public static adapt(appConfig: any): AppConfigModel { 9 | if (!appConfig) { 10 | return null; 11 | } 12 | return new AppConfigModel( 13 | get(appConfig, 'features', []), 14 | get(appConfig, 'session', null), 15 | ); 16 | } 17 | 18 | public static partition(appConfig: AppConfigModel): any { 19 | if (!appConfig) { 20 | return null; 21 | } 22 | return { 23 | features: get(appConfig, 'features', []), 24 | session: get(appConfig, 'session', null), 25 | }; 26 | } 27 | 28 | public adapt(item: any): AppConfigModel { 29 | return AppConfigAdapter.adapt(item); 30 | } 31 | 32 | public partition(appConfig: AppConfigModel): any { 33 | return AppConfigAdapter.partition(appConfig); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/route-guard.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Router, CanActivate, ActivatedRouteSnapshot} from '@angular/router'; 3 | 4 | import {UserService} from './user.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class RouteGuardService implements CanActivate { 10 | 11 | constructor(private readonly router: Router, 12 | private readonly userService: UserService) { 13 | } 14 | 15 | static readonly GUEST = '[RouteGuardService] Guest'; 16 | static readonly USER = '[RouteGuardService] User'; 17 | 18 | canActivate(route: ActivatedRouteSnapshot): boolean { 19 | 20 | const isSession = this.userService.session !== null; 21 | 22 | if (isSession && route.data.allow === RouteGuardService.GUEST) { 23 | this.router.navigate(route.data.redirect); 24 | return false; 25 | } 26 | 27 | if (!isSession && route.data.allow === RouteGuardService.USER) { 28 | this.router.navigate(route.data.redirect); 29 | return false; 30 | } 31 | 32 | return true; 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /monorepo-example-www/src/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-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /monorepo-example-api/src/config/local-config.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | 3 | import {BaseConfigService} from './config.module'; 4 | 5 | @Injectable() 6 | export class LocalConfigService implements BaseConfigService { 7 | public jwt = { 8 | signing_key: process.env.MONOREPO_EXAMPLE_JWT_SIGNINGKEY, 9 | }; 10 | public oauth = { 11 | github: { 12 | access_token_url: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_ACCESSTOKENURL, 13 | authorize_url: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_AUTHORIZEURL, 14 | client_id: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_CLIENTID, 15 | client_secret: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_CLIENTSECRET, 16 | redirect_uri: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_REDIRECTURI, 17 | user_agent_header: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_USERAGENTHEADER, 18 | user_profile_url: process.env.MONOREPO_EXAMPLE_OAUTH_GITHUB_USERPROFILEURL, 19 | }, 20 | }; 21 | public storage = { 22 | type: process.env.MONOREPO_EXAMPLE_STORAGE_TYPE, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /shared/src/adapters/session.adapter.ts: -------------------------------------------------------------------------------- 1 | import {get} from 'lodash'; 2 | 3 | import {SessionModel} from '../models'; 4 | import {Adapter} from './adapter'; 5 | 6 | export class SessionAdapter implements Adapter { 7 | 8 | public static adapt(session: any): SessionModel { 9 | if (!session) { 10 | return null; 11 | } 12 | return new SessionModel( 13 | get(session, 'id', null), 14 | get(session, 'user', null), 15 | get(session, 'userId', null), 16 | ); 17 | } 18 | 19 | public static partition(session: SessionModel): any { 20 | if (!session) { 21 | return null; 22 | } 23 | return { 24 | id: get(session, 'id', null), 25 | user: get(session, 'user', null), 26 | userId: get(session, 'userId', null), 27 | }; 28 | } 29 | 30 | public adapt(item: any): SessionModel { 31 | return SessionAdapter.adapt(item); 32 | } 33 | 34 | public partition(session: SessionModel): any { 35 | return SessionAdapter.partition(session); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {Routes, RouterModule} from '@angular/router'; 3 | 4 | import * as containersRoot from './containers'; 5 | import {RouteGuardService} from './services/route-guard.service'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | pathMatch: 'full', 11 | component: containersRoot.HomeComponent, 12 | }, 13 | { 14 | path: 'sign-in', 15 | component: containersRoot.SignInComponent, 16 | canActivate: [RouteGuardService], 17 | data: { 18 | allow: RouteGuardService.GUEST, 19 | redirect: ['/'], 20 | }, 21 | }, 22 | { 23 | path: 'settings', 24 | component: containersRoot.PageNotFoundComponent, 25 | canActivate: [RouteGuardService], 26 | data: { 27 | allow: RouteGuardService.USER, 28 | redirect: ['/sign-in'], 29 | }, 30 | }, 31 | { 32 | path: '**', 33 | component: containersRoot.PageNotFoundComponent, 34 | }, 35 | ]; 36 | 37 | @NgModule({ 38 | imports: [RouterModule.forRoot(routes)], 39 | exports: [RouterModule] 40 | }) 41 | export class AppRoutingModule { 42 | } 43 | -------------------------------------------------------------------------------- /monorepo-example-www/README.md: -------------------------------------------------------------------------------- 1 | # MonorepoExampleWww 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.0.4. 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 README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {SessionModel} from '@monorepo-example/shared'; 3 | import {Store} from '@ngrx/store'; 4 | import {Observable} from 'rxjs'; 5 | 6 | import * as appActions from './actions/app.actions'; 7 | import * as fromStore from './reducers'; 8 | 9 | @Component({ 10 | selector: 'app-app', 11 | templateUrl: './app.component.html', 12 | styleUrls: ['./app.component.scss'] 13 | }) 14 | export class AppComponent { 15 | 16 | appError$: Observable; 17 | appInstantiated$: Observable; 18 | appFeatures$: Observable; 19 | appSession$: Observable; 20 | 21 | constructor(private readonly store: Store) { 22 | this.appError$ = this.store.select(fromStore.selectAppError); 23 | this.appFeatures$ = this.store.select(fromStore.selectAppFeatures); 24 | this.appInstantiated$ = this.store.select(fromStore.selectAppInstantiated); 25 | this.appSession$ = this.store.select(fromStore.selectAppSession); 26 | } 27 | 28 | public doSignout(): void { 29 | this.store.dispatch(new appActions.UserClickedToolbarToSignout()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Angular Logo 5 |
6 |

Here are some links to help you start:

7 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monorepo-example 2 | 3 |

4 | 5 | Monorepo Screenshot 6 | 7 |

8 | 9 | ## Build instructions 10 | 11 | (Instructions written within a Linux environment!) 12 | 13 | 1. First run `yarn` and install all dependencies in each project: `monorepo-example-api`, `monorepo-example-www` and `shared` 14 | 2. Build our local dependencies first by running `yarn build` in the `shared` project. 15 | 3. Create [an OAuth GitHub application](https://github.com/settings/applications/new) to get a `client_id` and a `client_secret` to use in the next step. 16 | 4. Make a file called `.env` based off of the `.env-template` file in the `monorepo-example-api` project (use your new app's client info from the previous step!) The `.env` file is ignored by git and should not be checked in. 17 | 5. Run `yarn start:dev` in the `monorepo-example-api` project. 18 | 6. (In another terminal) run `yarn start` in the `monorepo-example-www` project. 19 | 7. Visit `http://localhost:4200` in your browser. 20 | 8. Sign into your application via GitHub and see the 'Sign In' button turn to a 'Sign Out' button. 21 | -------------------------------------------------------------------------------- /monorepo-example-api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import {MiddlewareConsumer, Module} from '@nestjs/common'; 2 | import {ConfigModule} from './config'; 3 | import {StorageModule} from './storage'; 4 | import * as cookieParser from 'cookie-parser'; 5 | require('dotenv').config(); 6 | 7 | import * as controllersRoot from './controllers'; 8 | import * as middlewaresRoot from './middlewares'; 9 | import * as servicesRoot from './services'; 10 | 11 | @Module({ 12 | imports: [ 13 | ConfigModule, 14 | StorageModule, 15 | ], 16 | controllers: [ 17 | controllersRoot.AppController, 18 | controllersRoot.ApiController, 19 | ], 20 | providers: [ 21 | servicesRoot.AppService, 22 | servicesRoot.UserService, 23 | servicesRoot.OauthService, 24 | ], 25 | }) 26 | export class AppModule { 27 | configure(consumer: MiddlewareConsumer) { 28 | consumer 29 | .apply(middlewaresRoot.CorsMiddleware) 30 | .with({credentials: true, origin: 'http://localhost:4200', preflightContinue: true, methods: 'GET,POST,PUT,DELETE,OPTIONS'}) 31 | .forRoutes(controllersRoot.ApiController); 32 | consumer 33 | .apply(cookieParser(), middlewaresRoot.JwtSessionMiddleware) 34 | .forRoutes(controllersRoot.ApiController); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /shared/src/adapters/user.adapter.ts: -------------------------------------------------------------------------------- 1 | import {get} from 'lodash'; 2 | 3 | import {UserModel} from '../models'; 4 | import {Adapter} from './adapter'; 5 | 6 | export class UserAdapter implements Adapter { 7 | 8 | public static adapt(user: any): UserModel { 9 | if (!user) { 10 | return null; 11 | } 12 | return new UserModel( 13 | get(user,'id', null), 14 | { 15 | oauth: { 16 | github: { 17 | id: get(user, 'auth.oauth.github.id', null), 18 | }, 19 | }, 20 | }, 21 | ); 22 | } 23 | 24 | public static partition(user: UserModel): any { 25 | if (!user) { 26 | return null; 27 | } 28 | return { 29 | id: get(user, 'id', null), 30 | auth: { 31 | oauth: { 32 | github: { 33 | id: get(user, 'auth.oauth.github.id', null), 34 | }, 35 | }, 36 | }, 37 | }; 38 | } 39 | 40 | public adapt(item: any): UserModel { 41 | return UserAdapter.adapt(item); 42 | } 43 | 44 | public partition(user: UserModel): any { 45 | return UserAdapter.partition(user); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/reducers/app.reducer.ts: -------------------------------------------------------------------------------- 1 | import {SessionModel} from '@monorepo-example/shared'; 2 | 3 | import {AppActions, AppActionTypes} from '../actions/app.actions'; 4 | 5 | export interface State { 6 | error: string; 7 | features: string[]; 8 | instantiated: boolean; 9 | session: SessionModel; 10 | } 11 | 12 | export const initialState: State = { 13 | error: null, 14 | features: null, 15 | instantiated: null, 16 | session: null, 17 | }; 18 | 19 | export function reducer(state = initialState, action: AppActions): State { 20 | 21 | switch (action.type) { 22 | 23 | case AppActionTypes.LoadInitialAppConfig: { 24 | if (action.payload.session === undefined) { 25 | return { 26 | ...state, 27 | error: 'Session information was not provided.', 28 | instantiated: true, 29 | }; 30 | } 31 | if (action.payload.features === undefined) { 32 | return { 33 | ...state, 34 | error: 'Features were not provided.', 35 | instantiated: true, 36 | }; 37 | } 38 | return { 39 | ...state, 40 | features: action.payload.features, 41 | instantiated: true, 42 | session: action.payload.session, 43 | }; 44 | } 45 | 46 | default: 47 | return state; 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /monorepo-example-api/src/config/config.module.ts: -------------------------------------------------------------------------------- 1 | import {Module} from '@nestjs/common'; 2 | 3 | import {LocalConfigService} from './local-config.service'; 4 | 5 | export abstract class BaseConfigService { 6 | public jwt: { 7 | signing_key: string; 8 | }; 9 | public oauth: { 10 | github: { 11 | access_token_url: string; 12 | authorize_url: string; 13 | client_id: string; 14 | client_secret: string; 15 | redirect_uri: string; 16 | user_agent_header: string; 17 | user_profile_url: string; 18 | }; 19 | }; 20 | public storage: { 21 | type: string; 22 | }; 23 | } 24 | 25 | @Module({ 26 | providers: [ 27 | { 28 | provide: BaseConfigService, 29 | useFactory: ConfigModule.Factory, 30 | } 31 | ], 32 | exports: [ 33 | BaseConfigService, 34 | ], 35 | }) 36 | export class ConfigModule { 37 | public static Factory() { 38 | switch (process.env['monorepoexampleCONFIG']) { 39 | case 'environment': { 40 | return null; 41 | } 42 | case 'dotenv': { 43 | return null; 44 | } 45 | default: { 46 | return new LocalConfigService(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /monorepo-example-api/src/storage/memory-storage.service.ts: -------------------------------------------------------------------------------- 1 | import {UserModel} from '@monorepo-example/shared'; 2 | import {Injectable} from '@nestjs/common'; 3 | import {Observable, of} from 'rxjs'; 4 | 5 | import {BaseStorageService} from './storage.module'; 6 | 7 | @Injectable() 8 | export class MemoryStorageService implements BaseStorageService { 9 | 10 | private users: UserModel[] = []; 11 | 12 | public createUser(user: UserModel): Observable { 13 | this.users.push(user); 14 | return of(user); 15 | } 16 | 17 | public getUser(id: string): Observable { 18 | for (const user of this.users) { 19 | if (user.id === id) { 20 | return of(user); 21 | } 22 | } 23 | return of(null); 24 | } 25 | 26 | public getUserByGithubId(githubId: string): Observable { 27 | for (const user of this.users) { 28 | if (!user.auth) { 29 | continue; 30 | } 31 | if (!user.auth.oauth) { 32 | continue; 33 | } 34 | if (!user.auth.oauth.github) { 35 | continue; 36 | } 37 | if (user.auth.oauth.github.id === githubId) { 38 | return of(user); 39 | } 40 | } 41 | return of(null); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /monorepo-example-api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": { 7 | "no-unused-expression": true 8 | }, 9 | "rules": { 10 | "eofline": false, 11 | "quotemark": [ 12 | true, 13 | "single" 14 | ], 15 | "indent": false, 16 | "member-access": [ 17 | false 18 | ], 19 | "ordered-imports": [ 20 | false 21 | ], 22 | "max-line-length": [ 23 | true, 24 | 150 25 | ], 26 | "member-ordering": [ 27 | false 28 | ], 29 | "curly": false, 30 | "interface-name": [ 31 | false 32 | ], 33 | "array-type": [ 34 | false 35 | ], 36 | "no-empty-interface": false, 37 | "no-empty": false, 38 | "arrow-parens": false, 39 | "object-literal-sort-keys": false, 40 | "no-unused-expression": false, 41 | "max-classes-per-file": [ 42 | false 43 | ], 44 | "variable-name": [ 45 | false 46 | ], 47 | "one-line": [ 48 | false 49 | ], 50 | "one-variable-per-declaration": [ 51 | false 52 | ] 53 | }, 54 | "rulesDirectory": [] 55 | } 56 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/services/app-config.service.ts: -------------------------------------------------------------------------------- 1 | import {HttpClient} from '@angular/common/http'; 2 | import {Injectable} from '@angular/core'; 3 | import {AppConfigModel} from '@monorepo-example/shared'; 4 | import {Store} from '@ngrx/store'; 5 | import {of} from 'rxjs'; 6 | import {catchError} from 'rxjs/operators'; 7 | 8 | import {environment} from '../../environments/environment'; 9 | import * as appActions from '../actions/app.actions'; 10 | import * as reducersRoot from '../reducers'; 11 | 12 | @Injectable({ 13 | providedIn: 'root' 14 | }) 15 | export class AppConfigService { 16 | 17 | constructor(private readonly store: Store) { 18 | } 19 | 20 | public static Initialize(httpClient: HttpClient, appConfigService: AppConfigService): () => Promise { 21 | return () => new Promise(resolve => { 22 | httpClient.get(environment.url.appConfig, {withCredentials: true}) 23 | .pipe( 24 | catchError(err => { 25 | console.error({err}); 26 | return of(null); 27 | }) 28 | ) 29 | .subscribe(appConfig => { 30 | appConfigService.load(appConfig); 31 | resolve(); 32 | }); 33 | }); 34 | } 35 | 36 | public load(appConfig: AppConfigModel): void { 37 | this.store.dispatch(new appActions.LoadInitialAppConfig(appConfig)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /monorepo-example-api/src/storage/storage.module.ts: -------------------------------------------------------------------------------- 1 | import {UserModel} from '@monorepo-example/shared'; 2 | import {Module} from '@nestjs/common'; 3 | import {Observable} from 'rxjs'; 4 | 5 | import {ConfigModule, BaseConfigService} from '../config'; 6 | import {MemoryStorageService} from './memory-storage.service'; 7 | 8 | export abstract class BaseStorageService { 9 | abstract createUser(user: UserModel): Observable; 10 | abstract getUser(id: string): Observable; 11 | abstract getUserByGithubId(githubId: string): Observable; 12 | } 13 | 14 | @Module({ 15 | imports: [ 16 | ConfigModule, 17 | ], 18 | providers: [ 19 | { 20 | provide: BaseStorageService, 21 | useFactory: StorageModule.Factory, 22 | inject: [BaseConfigService] 23 | } 24 | ], 25 | exports: [ 26 | BaseStorageService, 27 | ], 28 | }) 29 | export class StorageModule { 30 | public static Factory(baseConfigService: BaseConfigService) { 31 | switch (baseConfigService.storage.type) { 32 | case 'aws-dynamodb': { 33 | return null; 34 | } 35 | case 'azure-cosmosdb': { 36 | return null; 37 | } 38 | case 'local-sqlite': { 39 | return null; 40 | } 41 | default: { 42 | return new MemoryStorageService(); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /monorepo-example-api/src/middlewares/jwt-session.middleware.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, MiddlewareFunction, NestMiddleware} from '@nestjs/common'; 2 | import {Request} from 'express'; 3 | import {verify} from 'jsonwebtoken'; 4 | 5 | import {BaseConfigService} from '../config'; 6 | 7 | @Injectable() 8 | export class JwtSessionMiddleware implements NestMiddleware { 9 | 10 | constructor(private readonly baseConfigService: BaseConfigService) { 11 | } 12 | 13 | public static getToken(req: Request): string { 14 | if (req.headers.authorization) { 15 | const authorizationPair = req.headers.authorization.split(' '); 16 | if (authorizationPair[0] === 'Bearer') { 17 | return authorizationPair[1]; 18 | } 19 | } 20 | if (req.cookies && req.cookies.token) { 21 | return req.cookies.token; 22 | } 23 | if (req.query && req.query.token) { 24 | return req.query.token; 25 | } 26 | } 27 | 28 | resolve(...args: any[]): MiddlewareFunction { 29 | return (req, res, next) => { 30 | const access_token = JwtSessionMiddleware.getToken(req); 31 | if (access_token) { 32 | try { 33 | (req as any).session = verify(access_token, this.baseConfigService.jwt.signing_key); 34 | } catch(err) { 35 | (req as any).session = null; 36 | } 37 | } 38 | next() 39 | }; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {HttpClient, HttpClientModule} from '@angular/common/http'; 2 | import {APP_INITIALIZER, NgModule} from '@angular/core'; 3 | import {BrowserModule} from '@angular/platform-browser'; 4 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 5 | import {Store} from '@ngrx/store'; 6 | 7 | import * as containersRoot from './containers'; 8 | import * as servicesRoot from './services'; 9 | 10 | /** 11 | * The initial application entry point: 12 | */ 13 | import {AngularMaterialModule} from './angular-material.module'; 14 | import {AppComponent} from './app.component'; 15 | import {AppRoutingModule} from './app-routing.module'; 16 | import {AppStoreModule} from './app-store.module'; 17 | import {ToolbarComponentModule} from './components/toolbar/toolbar-component.module'; 18 | 19 | @NgModule({ 20 | imports: [ 21 | BrowserModule, 22 | HttpClientModule, 23 | BrowserAnimationsModule, 24 | AppStoreModule, 25 | /** 26 | * The initial application entry point: 27 | */ 28 | AngularMaterialModule, 29 | AppRoutingModule, 30 | ToolbarComponentModule, 31 | /** 32 | * View Containers 33 | */ 34 | containersRoot.HomeModule, 35 | containersRoot.PageNotFoundModule, 36 | containersRoot.SignInModule, 37 | ], 38 | declarations: [AppComponent], 39 | bootstrap: [AppComponent], 40 | providers: [ 41 | { 42 | provide: APP_INITIALIZER, 43 | useFactory: servicesRoot.AppConfigService.Initialize, 44 | deps: [HttpClient, servicesRoot.AppConfigService, Store], 45 | multi: true, 46 | }, 47 | ], 48 | }) 49 | export class AppModule { 50 | } 51 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import {UserAdapter, UserModel} from '@monorepo-example/shared'; 2 | import {Injectable} from '@nestjs/common'; 3 | import {Observable, of} from 'rxjs'; 4 | import {switchMap} from 'rxjs/operators'; 5 | import * as uuid from 'uuid/v4'; 6 | 7 | import {BaseStorageService} from '../storage'; 8 | 9 | @Injectable() 10 | export class UserService { 11 | 12 | constructor(private readonly baseStorageService: BaseStorageService) { 13 | } 14 | 15 | public createUser(user: any = {}): Observable { 16 | return this.baseStorageService.createUser(UserAdapter.adapt({ 17 | id: uuid(), 18 | ...user, 19 | })); 20 | } 21 | 22 | public getUserById(id: string): Observable { 23 | return this.baseStorageService.getUser(id); 24 | } 25 | 26 | public getUserByGithubId(githubId: string, createIfNotExists?: boolean): Observable { 27 | return this.baseStorageService.getUserByGithubId(githubId) 28 | .pipe( 29 | switchMap((user: UserModel) => { 30 | if (user) { 31 | return of(user); 32 | } 33 | return this.createUser({ 34 | auth: { 35 | oauth: { 36 | github: { 37 | id: githubId, 38 | }, 39 | }, 40 | }, 41 | }); 42 | }), 43 | ); 44 | } 45 | 46 | public updateUser(user: UserModel, updates: UserModel): Observable { 47 | return of(user); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/sign-in/sign-in.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {FormControl, Validators} from '@angular/forms'; 3 | import {SessionModel} from '@monorepo-example/shared'; 4 | import {Store} from '@ngrx/store'; 5 | import {Observable} from 'rxjs'; 6 | 7 | import {environment} from '../../../environments/environment'; 8 | import * as fromStore from '../../reducers'; 9 | 10 | @Component({ 11 | selector: 'app-sign-in', 12 | templateUrl: './sign-in.component.html', 13 | styleUrls: ['./sign-in.component.css'] 14 | }) 15 | export class SignInComponent { 16 | 17 | appError$: Observable; 18 | appInstantiated$: Observable; 19 | appFeatures$: Observable; 20 | appSession$: Observable; 21 | urlSigninGithub = environment.url.signinGithub; 22 | 23 | email = new FormControl('', [Validators.required, Validators.email]); 24 | password = new FormControl('', [Validators.required]); 25 | 26 | constructor(private readonly store: Store) { 27 | this.appError$ = this.store.select(fromStore.selectAppError); 28 | this.appFeatures$ = this.store.select(fromStore.selectAppFeatures); 29 | this.appInstantiated$ = this.store.select(fromStore.selectAppInstantiated); 30 | this.appSession$ = this.store.select(fromStore.selectAppSession); 31 | } 32 | 33 | getEmailErrorMessage() { 34 | return this.email.hasError('required') ? 'You must enter a value' 35 | : this.email.hasError('email') ? 'Not a valid email' 36 | : ''; 37 | } 38 | 39 | getPasswordErrorMessage() { 40 | return this.email.hasError('required') ? 'You must enter a value' 41 | : this.email.hasError('email') ? 'Not a valid email' 42 | : ''; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /monorepo-example-www/src/app/containers/sign-in/sign-in.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
16 | 17 |
18 | 19 | 20 | Sign In 21 | caption 22 | 23 | 24 | 25 | GitHub 27 | 28 |
29 | 30 | 34 | {{getEmailErrorMessage()}} 35 | 36 |
37 |
38 | 39 | 43 | {{getPasswordErrorMessage()}} 44 | 45 |
46 |
47 | 48 | 49 | 50 |
51 |
52 | 53 |
54 | -------------------------------------------------------------------------------- /monorepo-example-www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo-example-www", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --live-reload false", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^7.0.2", 15 | "@angular/cdk": "^7.0.2", 16 | "@angular/common": "~7.0.0", 17 | "@angular/compiler": "~7.0.0", 18 | "@angular/core": "~7.0.0", 19 | "@angular/flex-layout": "^7.0.0-beta.19", 20 | "@angular/forms": "~7.0.0", 21 | "@angular/http": "~7.0.0", 22 | "@angular/material": "^7.0.2", 23 | "@angular/platform-browser": "~7.0.0", 24 | "@angular/platform-browser-dynamic": "~7.0.0", 25 | "@angular/router": "~7.0.0", 26 | "@monorepo-example/shared": "../shared", 27 | "@ngrx/effects": "^6.1.2", 28 | "@ngrx/entity": "^6.1.2", 29 | "@ngrx/store": "^6.1.2", 30 | "@ngrx/store-devtools": "^6.1.2", 31 | "core-js": "^2.5.4", 32 | "hammerjs": "^2.0.8", 33 | "rxjs": "~6.3.3", 34 | "zone.js": "~0.8.26" 35 | }, 36 | "devDependencies": { 37 | "@angular-devkit/build-angular": "~0.10.0", 38 | "@angular/cli": "~7.0.4", 39 | "@angular/compiler-cli": "~7.0.0", 40 | "@angular/language-service": "~7.0.0", 41 | "@ngrx/schematics": "^6.1.2", 42 | "@types/jasmine": "~2.8.8", 43 | "@types/jasminewd2": "~2.0.3", 44 | "@types/node": "~8.9.4", 45 | "codelyzer": "~4.5.0", 46 | "jasmine-core": "~2.99.1", 47 | "jasmine-spec-reporter": "~4.2.1", 48 | "karma": "~3.0.0", 49 | "karma-chrome-launcher": "~2.2.0", 50 | "karma-coverage-istanbul-reporter": "~2.0.1", 51 | "karma-jasmine": "~1.1.2", 52 | "karma-jasmine-html-reporter": "^0.2.2", 53 | "protractor": "~5.4.0", 54 | "ts-node": "~7.0.0", 55 | "tslint": "~5.11.0", 56 | "typescript": "~3.1.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /monorepo-example-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo-example-api", 3 | "version": "0.0.0", 4 | "description": "The backend API code for the monorepo-example website.", 5 | "author": "James Allen", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "nest": "nest", 9 | "format": "prettier --write \"src/**/*.ts\"", 10 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 11 | "start:dev": "nodemon", 12 | "start:debug": "nodemon --config nodemon-debug.json", 13 | "prestart:prod": "rimraf dist && tsc", 14 | "start:prod": "node dist/main.js", 15 | "start:hmr": "node dist/server", 16 | "lint": "tslint -p tsconfig.json -c tslint.json", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:e2e": "jest --config ./test/jest-e2e.json", 21 | "webpack": "webpack --config webpack.config.js" 22 | }, 23 | "dependencies": { 24 | "@monorepo-example/shared": "../shared", 25 | "@nestjs/cli": "^5.6.2", 26 | "@nestjs/common": "^5.1.0", 27 | "@nestjs/core": "^5.1.0", 28 | "async": "^2.6.1", 29 | "cookie-parser": "^1.4.3", 30 | "cors": "^2.8.5", 31 | "dotenv": "^6.1.0", 32 | "jsonwebtoken": "^8.4.0", 33 | "lodash": "^4.17.11", 34 | "reflect-metadata": "^0.1.12", 35 | "request": "^2.88.0", 36 | "rxjs": "^6.2.2", 37 | "typescript": "^3.0.1", 38 | "uuid": "^3.3.2" 39 | }, 40 | "devDependencies": { 41 | "@nestjs/testing": "^5.1.0", 42 | "@types/async": "^2.0.50", 43 | "@types/cookie-parser": "^1.4.1", 44 | "@types/cors": "^2.8.4", 45 | "@types/express": "^4.16.0", 46 | "@types/jest": "^23.3.1", 47 | "@types/jsonwebtoken": "^8.3.0", 48 | "@types/lodash": "^4.14.118", 49 | "@types/node": "^10.7.1", 50 | "@types/request": "^2.48.1", 51 | "@types/supertest": "^2.0.5", 52 | "@types/uuid": "^3.4.4", 53 | "jest": "^23.5.0", 54 | "nodemon": "^1.18.3", 55 | "prettier": "^1.14.2", 56 | "rimraf": "^2.6.2", 57 | "supertest": "^3.1.0", 58 | "ts-jest": "^23.1.3", 59 | "ts-loader": "^4.4.2", 60 | "ts-node": "^7.0.1", 61 | "tsconfig-paths": "^3.5.0", 62 | "tslint": "5.11.0", 63 | "webpack": "^4.16.5", 64 | "webpack-cli": "^3.1.0", 65 | "webpack-node-externals": "^1.7.2" 66 | }, 67 | "jest": { 68 | "moduleFileExtensions": [ 69 | "js", 70 | "json", 71 | "ts" 72 | ], 73 | "rootDir": "src", 74 | "testRegex": ".spec.ts$", 75 | "transform": { 76 | "^.+\\.(t|j)s$": "ts-jest" 77 | }, 78 | "coverageDirectory": "../coverage", 79 | "testEnvironment": "node" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /monorepo-example-api/src/controllers/api.controller.ts: -------------------------------------------------------------------------------- 1 | import {AppConfigAdapter, AppConfigModel, SessionAdapter, SessionModel} from '@monorepo-example/shared'; 2 | import {Body, Controller, Get, Options, Post, Query, Res, Session} from '@nestjs/common'; 3 | import {Response} from 'express'; 4 | import {sign} from 'jsonwebtoken'; 5 | import {get} from 'lodash'; 6 | import {of}from 'rxjs'; 7 | import {catchError, switchMap} from 'rxjs/operators'; 8 | import * as uuid from 'uuid/v4'; 9 | 10 | import {BaseConfigService} from '../config'; 11 | import {OauthService, UserService} from '../services'; 12 | 13 | @Controller('api') 14 | export class ApiController { 15 | 16 | constructor(private readonly baseConfigService: BaseConfigService, 17 | private readonly oauthService: OauthService, 18 | private readonly userService: UserService) { 19 | } 20 | 21 | @Options('*') 22 | apiOptions(): void { 23 | } 24 | 25 | @Get('app-config') 26 | apiGetAppConfig(@Session() session: SessionModel = null): AppConfigModel { 27 | if (!session) { 28 | return AppConfigAdapter.adapt({}); 29 | } 30 | return AppConfigAdapter.adapt({ 31 | session: SessionAdapter.adapt({ 32 | id: session.id, 33 | userId: session.userId, 34 | }), 35 | }); 36 | } 37 | 38 | @Post('sign-out') 39 | apiPostSignOut(@Body() body, 40 | @Res() res: Response, 41 | @Session() session: SessionModel = null): any { 42 | if (get(body, 'id') === get(session, 'id')) { 43 | res.clearCookie('token'); 44 | res.send({error: null}); 45 | } else { 46 | res.send({error: true}); 47 | } 48 | } 49 | 50 | @Get('sign-in/github') 51 | apiGetSignInGithub(@Res() res: Response): void { 52 | const state = Date.now().toString(); 53 | this.oauthService.githubRedirectUrl(state) 54 | .subscribe((redirect_url) => res.redirect(redirect_url)); 55 | } 56 | 57 | @Get('oauth/github') 58 | apiGetOauthGithub(@Session() session: SessionModel = null, 59 | @Query('code') code: string, 60 | @Query('state') state: string, 61 | @Res() res: Response): void { 62 | this.oauthService.githubAuthenticate(code) 63 | .pipe( 64 | catchError((err) => { 65 | console.log({err}); 66 | return of(null); 67 | }), 68 | switchMap((githubId: string) => this.userService.getUserByGithubId(githubId, true)), 69 | ) 70 | .subscribe( 71 | (user) => { 72 | const userId = user.id; 73 | const encode = String; 74 | const httpOnly = true; 75 | const id = uuid(); 76 | const jwtToken = sign({userId, id}, this.baseConfigService.jwt.signing_key); 77 | res.cookie('token', jwtToken, {encode, httpOnly}); 78 | res.redirect('http://localhost:4200/'); 79 | } 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /monorepo-example-www/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 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-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /monorepo-example-www/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** 38 | * If the application will be indexed by Google Search, the following is required. 39 | * Googlebot uses a renderer based on Chrome 41. 40 | * https://developers.google.com/search/docs/guides/rendering 41 | **/ 42 | // import 'core-js/es6/array'; 43 | 44 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 45 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 46 | 47 | /** IE10 and IE11 requires the following for the Reflect API. */ 48 | // import 'core-js/es6/reflect'; 49 | 50 | /** 51 | * Web Animations `@angular/platform-browser/animations` 52 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 53 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 54 | **/ 55 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 56 | 57 | /** 58 | * By default, zone.js will patch all possible macroTask and DomEvents 59 | * user can disable parts of macroTask/DomEvents patch by setting following flags 60 | */ 61 | 62 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 63 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 64 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 65 | 66 | /* 67 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 68 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 69 | */ 70 | // (window as any).__Zone_enable_cross_context_check = true; 71 | 72 | /*************************************************************************************************** 73 | * Zone JS is required by default for Angular itself. 74 | */ 75 | import 'zone.js/dist/zone'; // Included with Angular CLI. 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /monorepo-example-api/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 | # incremental rebuild (webpack) 47 | $ npm run webpack 48 | $ npm run start:hmr 49 | 50 | # production mode 51 | $ npm run start:prod 52 | ``` 53 | 54 | ## Test 55 | 56 | ```bash 57 | # unit tests 58 | $ npm run test 59 | 60 | # e2e tests 61 | $ npm run test:e2e 62 | 63 | # test coverage 64 | $ npm run test:cov 65 | ``` 66 | 67 | ## Support 68 | 69 | 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). 70 | 71 | ## Stay in touch 72 | 73 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 74 | - Website - [https://nestjs.com](https://nestjs.com/) 75 | - Twitter - [@nestframework](https://twitter.com/nestframework) 76 | 77 | ## License 78 | 79 | Nest is [MIT licensed](LICENSE). 80 | -------------------------------------------------------------------------------- /monorepo-example-api/src/services/oauth.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | import {waterfall} from 'async'; 3 | import * as request from 'request'; 4 | import {Observable, of, Subscriber} from 'rxjs'; 5 | import {URL} from 'url'; 6 | 7 | import {BaseConfigService} from '../config'; 8 | 9 | @Injectable() 10 | export class OauthService { 11 | 12 | constructor(private readonly baseConfigService: BaseConfigService) { 13 | } 14 | 15 | public githubRedirectUrl(state?: string): Observable { 16 | const url = new URL(this.baseConfigService.oauth.github.authorize_url); 17 | url.searchParams.set('client_id', this.baseConfigService.oauth.github.client_id); 18 | url.searchParams.set('redirect_uri', this.baseConfigService.oauth.github.redirect_uri); 19 | if (state) { 20 | url.searchParams.set('state', state); 21 | } 22 | return of(url.toString()); 23 | } 24 | 25 | public githubAuthenticate(code: string): Observable { 26 | return Observable.create((observer: Subscriber) => { 27 | waterfall( 28 | [ 29 | (next) => { 30 | request.post({ 31 | url: this.baseConfigService.oauth.github.access_token_url, 32 | json: { 33 | client_id: this.baseConfigService.oauth.github.client_id, 34 | client_secret: this.baseConfigService.oauth.github.client_secret, 35 | code, 36 | }, 37 | }, (error, r, body: any) => { 38 | if (error) { 39 | next(error); 40 | } else if (!body) { 41 | next(new Error('oAuth: Bad access token response from GitHub')); 42 | } else if (!body.access_token) { 43 | next(new Error('oAuth: No access token from GitHub.')); 44 | } else { 45 | next(null, body.access_token); 46 | } 47 | }) 48 | }, 49 | (access_token: string, next) => { 50 | request.get({ 51 | url: this.baseConfigService.oauth.github.user_profile_url, 52 | json: true, 53 | headers: { 54 | 'User-Agent': this.baseConfigService.oauth.github.user_agent_header, 55 | Authorization: `token ${access_token}`, 56 | }, 57 | }, (error, r, body) => { 58 | if (error) { 59 | next(error); 60 | } else if (!body) { 61 | next(new Error('oAuth: Bad profile response from GitHub.')) 62 | } else if (!body.id) { 63 | next(new Error('oAuth: No profile from GitHub profile.')) 64 | } else { 65 | next(null, body.id.toString()); 66 | } 67 | }); 68 | }, 69 | ], 70 | (error, github_id: string) => { 71 | if (error) { 72 | observer.error(error); 73 | } else { 74 | observer.next(github_id); 75 | observer.complete(); 76 | } 77 | } 78 | ); 79 | }); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /shared/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/lodash@^4.14.116": 6 | version "4.14.118" 7 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.118.tgz#247bab39bfcc6d910d4927c6e06cbc70ec376f27" 8 | integrity sha512-iiJbKLZbhSa6FYRip/9ZDX6HXhayXLDGY2Fqws9cOkEQ6XeKfaxB0sC541mowZJueYyMnVUmmG+al5/4fCDrgw== 9 | 10 | balanced-match@^1.0.0: 11 | version "1.0.0" 12 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 13 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 14 | 15 | brace-expansion@^1.1.7: 16 | version "1.1.11" 17 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 18 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 19 | dependencies: 20 | balanced-match "^1.0.0" 21 | concat-map "0.0.1" 22 | 23 | concat-map@0.0.1: 24 | version "0.0.1" 25 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 26 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 27 | 28 | fs.realpath@^1.0.0: 29 | version "1.0.0" 30 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 31 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 32 | 33 | glob@^7.0.5: 34 | version "7.1.3" 35 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 36 | integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 37 | dependencies: 38 | fs.realpath "^1.0.0" 39 | inflight "^1.0.4" 40 | inherits "2" 41 | minimatch "^3.0.4" 42 | once "^1.3.0" 43 | path-is-absolute "^1.0.0" 44 | 45 | inflight@^1.0.4: 46 | version "1.0.6" 47 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 48 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 49 | dependencies: 50 | once "^1.3.0" 51 | wrappy "1" 52 | 53 | inherits@2: 54 | version "2.0.3" 55 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 56 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 57 | 58 | lodash@^4.17.11: 59 | version "4.17.11" 60 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" 61 | integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== 62 | 63 | minimatch@^3.0.4: 64 | version "3.0.4" 65 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 66 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 67 | dependencies: 68 | brace-expansion "^1.1.7" 69 | 70 | once@^1.3.0: 71 | version "1.4.0" 72 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 73 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 74 | dependencies: 75 | wrappy "1" 76 | 77 | path-is-absolute@^1.0.0: 78 | version "1.0.1" 79 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 80 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 81 | 82 | rimraf@^2.6.2: 83 | version "2.6.2" 84 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" 85 | integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== 86 | dependencies: 87 | glob "^7.0.5" 88 | 89 | typescript@^3.1.6: 90 | version "3.1.6" 91 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" 92 | integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== 93 | 94 | wrappy@1: 95 | version "1.0.2" 96 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 97 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 98 | -------------------------------------------------------------------------------- /monorepo-example-www/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "monorepo-example-www": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "styleext": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/monorepo-example-www", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 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 | "aot": true, 49 | "extractLicenses": true, 50 | "vendorChunk": false, 51 | "buildOptimizer": true, 52 | "budgets": [ 53 | { 54 | "type": "initial", 55 | "maximumWarning": "2mb", 56 | "maximumError": "5mb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "monorepo-example-www:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "monorepo-example-www:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "monorepo-example-www:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "src/tsconfig.spec.json", 85 | "karmaConfig": "src/karma.conf.js", 86 | "styles": [ 87 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 88 | "src/styles.scss" 89 | ], 90 | "scripts": [], 91 | "assets": [ 92 | "src/favicon.ico", 93 | "src/assets" 94 | ] 95 | } 96 | }, 97 | "lint": { 98 | "builder": "@angular-devkit/build-angular:tslint", 99 | "options": { 100 | "tsConfig": [ 101 | "src/tsconfig.app.json", 102 | "src/tsconfig.spec.json" 103 | ], 104 | "exclude": [ 105 | "**/node_modules/**" 106 | ] 107 | } 108 | } 109 | } 110 | }, 111 | "monorepo-example-www-e2e": { 112 | "root": "e2e/", 113 | "projectType": "application", 114 | "prefix": "", 115 | "architect": { 116 | "e2e": { 117 | "builder": "@angular-devkit/build-angular:protractor", 118 | "options": { 119 | "protractorConfig": "e2e/protractor.conf.js", 120 | "devServerTarget": "monorepo-example-www:serve" 121 | }, 122 | "configurations": { 123 | "production": { 124 | "devServerTarget": "monorepo-example-www:serve:production" 125 | } 126 | } 127 | }, 128 | "lint": { 129 | "builder": "@angular-devkit/build-angular:tslint", 130 | "options": { 131 | "tsConfig": "e2e/tsconfig.e2e.json", 132 | "exclude": [ 133 | "**/node_modules/**" 134 | ] 135 | } 136 | } 137 | } 138 | } 139 | }, 140 | "defaultProject": "monorepo-example-www" 141 | } 142 | --------------------------------------------------------------------------------